V1.0
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
# Ratchet & Clank (PAL v2.00): build kit
|
||||
|
||||
A static recompilation of Ratchet & Clank (`SCES_509.16`) for Windows (and Linux, for development).
|
||||
|
||||
`build.py` reads the game's executable and level programs from your disc image, generates C from them (the decomp's own C where it has it, everything else translated instruction by instruction by `tools/recomp.py`, the VU microcode by `tools/vu_recomp.py`), and compiles it with the host engine in `host/` into `build/windows/rac1_game.dll` and `rac1_wrapper.exe`.
|
||||
|
||||
```
|
||||
python build.py --platform windows --iso "Ratchet & Clank (Europe).iso" --repo ../rac1-decomp --levels 0,1,...,18
|
||||
```
|
||||
|
||||
Needs Python 3, `pip install rabbitizer ziglang`. The Project Solana launcher runs this for players with its own copies of both.
|
||||
|
||||
| Path | What |
|
||||
|---|---|
|
||||
| `tools/gen_host.py` | the decomp's C made host-ready (types, calls between C and translated code) |
|
||||
| `tools/recomp.py` | MIPS (R5900) to C, the executable and each level |
|
||||
| `tools/vu_recomp.py` | VU0/VU1 microprograms to C |
|
||||
| `tools/level_dump.py` | a level's program from the disc image |
|
||||
| `host/src/gs/` | the Graphics Synthesizer: software and OpenGL 3.3 (upscaling: `UYA_SCALE`) |
|
||||
| `host/src/gfx/` | VIF, GIF and the VU runtime |
|
||||
| `host/src/sdk/`, `host/src/rac1/` | Sony library entry points, 989snd and the SPU2, levels, boot |
|
||||
| `host/overrides/` | hand-ported assembly functions |
|
||||
| `launcher/` | `rac1_wrapper.exe`: reserves PS2 RAM at its real addresses, joins split DLLs, runs the DLL |
|
||||
| `docs/LEVELS.md` | how level programs are built in |
|
||||
|
||||
Environment variables for testing (`UYA_FPSLOG`, `UYA_GSREC`, `UYA_LEVEL`, `UYA_SCALE`, ...) are described where they are read.
|
||||
@@ -0,0 +1,259 @@
|
||||
#!/usr/bin/env python3
|
||||
"""build.py: generate host sources from OpenRAC rac1-decomp and build the RAC1 Wrapper.
|
||||
|
||||
usage: python build.py [--repo PATH_TO_DECOMP] [--platform linux|windows] [-j N]
|
||||
[--iso PATH_TO_DISC_IMAGE] [--levels 00,01,...|none]
|
||||
|
||||
Needs Python 3 and zig (pip install ziglang, or zig on PATH). The decomp repo
|
||||
is only read. Output: build/<platform>/rac1_wrapper[.exe]
|
||||
|
||||
Level programs (docs/LEVELS.md): each level listed in --levels (default 00,
|
||||
Veldin 1) is compiled from src/overlays and its own bytes. Those bytes come
|
||||
from your disc image (--iso, else RAC1_ISO, else iso= in
|
||||
build/<platform>/rac1_wrapper.cfg), or from an OpenRAC dump in the decomp
|
||||
repo (baserom/overlays/level_NN); they are extracted to build/levels/ and
|
||||
never leave your machine.
|
||||
"""
|
||||
import argparse, glob, json, os, shutil, subprocess, sys
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--repo", default=os.environ.get("UYA_DECOMP", os.path.join(HERE, "..", "rac1-decomp-main")))
|
||||
ap.add_argument("--platform", default="windows" if os.name == "nt" else "linux")
|
||||
ap.add_argument("-j", type=int, default=os.cpu_count() or 2)
|
||||
ap.add_argument("-O", default="2")
|
||||
ap.add_argument("--iso", default=None)
|
||||
ap.add_argument("--levels", default="00")
|
||||
a = ap.parse_args()
|
||||
|
||||
zig = [shutil.which("zig")] if shutil.which("zig") else [sys.executable, "-m", "ziglang"]
|
||||
target = {"linux": "x86-linux-musl", "windows": "x86-windows-gnu"}[a.platform]
|
||||
out = os.path.join(HERE, "build", a.platform)
|
||||
gen = os.path.join(out, "gen")
|
||||
stage = os.path.join(out, "gen.new")
|
||||
obj = os.path.join(out, "obj")
|
||||
os.makedirs(obj, exist_ok=True)
|
||||
levels = [] if a.levels in ("", "none") else sorted({int(x) for x in a.levels.split(",")})
|
||||
|
||||
|
||||
def find_iso():
|
||||
if a.iso: return a.iso
|
||||
if os.environ.get("RAC1_ISO"): return os.environ["RAC1_ISO"]
|
||||
cfg = os.path.join(out, "rac1_wrapper.cfg")
|
||||
if os.path.exists(cfg):
|
||||
for l in open(cfg, errors="replace"):
|
||||
if l.strip().startswith("iso="): return l.strip()[4:].strip()
|
||||
return None
|
||||
|
||||
|
||||
def sync(src, dst):
|
||||
"""copy the freshly generated tree over gen/, touching only files that
|
||||
changed, so unchanged sources keep their objects"""
|
||||
os.makedirs(dst, exist_ok=True)
|
||||
keep = set()
|
||||
for root, _, fs in os.walk(src):
|
||||
for f in fs:
|
||||
s = os.path.join(root, f)
|
||||
rel = os.path.relpath(s, src)
|
||||
d = os.path.join(dst, rel)
|
||||
keep.add(rel)
|
||||
os.makedirs(os.path.dirname(d), exist_ok=True)
|
||||
if not os.path.exists(d) or open(s, "rb").read() != open(d, "rb").read():
|
||||
shutil.copyfile(s, d)
|
||||
for root, _, fs in os.walk(dst):
|
||||
for f in fs:
|
||||
rel = os.path.relpath(os.path.join(root, f), dst)
|
||||
if rel not in keep: os.remove(os.path.join(root, f))
|
||||
shutil.rmtree(src)
|
||||
|
||||
|
||||
def iso_file(iso, name):
|
||||
"""a file in the disc image's root directory (ISO 9660)"""
|
||||
import struct
|
||||
with open(iso, "rb") as f:
|
||||
f.seek(16 * 2048); pvd = f.read(2048)
|
||||
if pvd[1:6] != b"CD001": return None
|
||||
dlsn, dlen = struct.unpack_from("<I", pvd, 158)[0], struct.unpack_from("<I", pvd, 166)[0]
|
||||
f.seek(dlsn * 2048); d = f.read(dlen)
|
||||
o = 0
|
||||
while o < len(d):
|
||||
n = d[o]
|
||||
if n == 0: o = (o // 2048 + 1) * 2048; continue
|
||||
fn = d[o + 33:o + 33 + d[o + 32]].decode("ascii", "replace").split(";")[0]
|
||||
if fn == name:
|
||||
lsn, size = struct.unpack_from("<I", d, o + 2)[0], struct.unpack_from("<I", d, o + 10)[0]
|
||||
f.seek(lsn * 2048); return f.read(size)
|
||||
o += n
|
||||
return None
|
||||
|
||||
|
||||
env = dict(os.environ, UYA_PLATFORM=a.platform, RAC_PLATFORM=a.platform)
|
||||
# the static recompilation is built from the game itself: the executable comes
|
||||
# from the disc image (the decomp repo only lends its symbol and function lists)
|
||||
if find_iso():
|
||||
_exe = iso_file(find_iso(), "SCES_509.16")
|
||||
if _exe:
|
||||
os.makedirs(os.path.join(HERE, "build", "disc"), exist_ok=True)
|
||||
_p = os.path.join(HERE, "build", "disc", "SCES_509.16")
|
||||
if not os.path.exists(_p) or open(_p, "rb").read() != _exe: open(_p, "wb").write(_exe)
|
||||
env["UYA_EXE"] = _p
|
||||
print("executable read from the disc image:", find_iso())
|
||||
shutil.rmtree(stage, ignore_errors=True)
|
||||
py = sys.executable
|
||||
def progress(phase, i, n):
|
||||
"""a line the Project Solana launcher reads for its progress bar"""
|
||||
print("[progress] %s %d %d" % (phase, i, n), flush=True)
|
||||
progress("prepare", 0, 3)
|
||||
subprocess.run([py, os.path.join(HERE, "tools", "gen_host.py"), a.repo, stage], check=True, env=env)
|
||||
dumps = os.path.join(HERE, "build", "levels")
|
||||
# the level programs first: the executable's translation looks at the core
|
||||
# addresses their code calls (tools/recomp.py)
|
||||
for lv in levels:
|
||||
cmd = [py, os.path.join(HERE, "tools", "level_dump.py"), dumps, str(lv), "--repo", a.repo]
|
||||
if find_iso(): cmd += ["--iso", find_iso()]
|
||||
subprocess.run(cmd, check=True)
|
||||
progress("prepare", 1, 3)
|
||||
subprocess.run([py, os.path.join(HERE, "tools", "recomp.py"), stage, a.repo], check=True, env=env)
|
||||
progress("prepare", 2, 3)
|
||||
# VU0/VU1 microprograms (.vutext) to C
|
||||
subprocess.run([py, os.path.join(HERE, "tools", "vu_recomp.py"), stage, a.repo], check=True, env=env)
|
||||
level_info = []
|
||||
for k, lv in enumerate(levels):
|
||||
progress("levels", k, len(levels))
|
||||
LV = "%02d" % lv
|
||||
lg = os.path.join(stage, "l" + LV)
|
||||
subprocess.run([py, os.path.join(HERE, "tools", "gen_host.py"), a.repo, lg, "--level", LV, "--exe-gen", stage], check=True, env=env)
|
||||
subprocess.run([py, os.path.join(HERE, "tools", "recomp.py"), lg, a.repo, "--level", LV, "--dump", dumps, "--exe-gen", stage],
|
||||
check=True, env=env)
|
||||
man = json.load(open(os.path.join(dumps, "level_" + LV, "manifest.json")))
|
||||
text = [r for r in man["records"] if r["name"] == "text"][0]
|
||||
level_info.append((LV, man["entry_point"], text["address"], text["bytes"]))
|
||||
# the levels built in: ParseBin looks a loaded program up here (host/src/rac1/levels.c)
|
||||
with open(os.path.join(stage, "level_table.c"), "w") as f:
|
||||
f.write('/* generated by build.py: the level programs in this build */\n#include "rac1_levels.h"\n')
|
||||
for LV, *_ in level_info:
|
||||
f.write("extern const uya_tramp_t uya_tramp_table_l%s[]; extern const int uya_tramp_count_l%s;\n" % (LV, LV))
|
||||
f.write("extern const rac1_rc_row_t rc%s_table[]; extern const int rc%s_table_count;\n" % (LV, LV))
|
||||
f.write("const rac1_level_t rac1_levels[] = {\n")
|
||||
for LV, entry, ta, tb in level_info:
|
||||
f.write(" {%d, 0x%08X, 0x%08X, 0x%X, uya_tramp_table_l%s, &uya_tramp_count_l%s, rc%s_table, &rc%s_table_count},\n"
|
||||
% (int(LV), entry, ta, tb, LV, LV, LV, LV))
|
||||
if not level_info: f.write(" {-1, 0, 0, 0, 0, 0, 0, 0},\n")
|
||||
f.write("};\nconst int rac1_level_count = %d;\n" % len(level_info))
|
||||
sync(stage, gen)
|
||||
|
||||
common = ["-target", target, "-O" + a.O, "-g", "-march=nehalem", "-mfpmath=sse", "-malign-double", "-fno-strict-aliasing", "-fwrapv",
|
||||
"-fno-PIE", "-fno-stack-protector", "-I", os.path.join(gen, "include"), "-I", os.path.join(HERE, "host", "include")]
|
||||
# frontbin code: keep it permissive, it was written for gcc 2.95
|
||||
menu_flags = ["-w", "-Wno-error=incompatible-pointer-types", "-Wno-error=int-conversion",
|
||||
"-Wno-error=implicit-function-declaration", "-Wno-error=implicit-int",
|
||||
"-Wno-error=incompatible-function-pointer-types", "-Wno-error=return-type",
|
||||
"-fno-builtin",
|
||||
# no inlining across the game's functions: the decomp names overlapping PS2 data
|
||||
# with separate symbols (D_001997D0 is a struct whose +0x2C is also D_001997FC),
|
||||
# which the compiler takes for distinct objects. A call keeps such stores and loads
|
||||
# in order; an inlined body does not (func_001EB300 inlined into func_001EABE8 kept
|
||||
# the help-text count in a register, so the text tables were never relocated)
|
||||
"-fno-inline-functions",
|
||||
# the game's C keeps 16-byte vectors (u128, qcopy) in locals; the x86 stack is
|
||||
# not 16-byte aligned on every path into it (translated code, thunks), and
|
||||
# an aligned load of a misaligned local faults (func_L00_001FE940 on Veldin's
|
||||
# first frame): every function realigns its own frame
|
||||
"-mstackrealign"]
|
||||
jobs = [] # (source, extra flags, object name, forced header)
|
||||
for c in sorted(glob.glob(os.path.join(gen, "*.c"))):
|
||||
jobs.append((c, menu_flags if os.path.basename(c) not in ("tramp_table.c", "level_table.c") else [], None, None))
|
||||
jobs.append((os.path.join(gen, "abs_syms.s"), [], None, None))
|
||||
for c in sorted(glob.glob(os.path.join(HERE, "host", "overrides", "*.c"))):
|
||||
jobs.append((c, menu_flags, None, None))
|
||||
for c in sorted(glob.glob(os.path.join(HERE, "host", "src", "**", "*.c"), recursive=True)):
|
||||
jobs.append((c, ["-Wall", "-Wno-unused"], None, None))
|
||||
for LV, *_ in level_info:
|
||||
lg = os.path.join(gen, "l" + LV)
|
||||
hdr = os.path.join(lg, "l%s_names.h" % LV)
|
||||
os.makedirs(os.path.join(obj, "l" + LV), exist_ok=True)
|
||||
for c in sorted(glob.glob(os.path.join(lg, "*.c"))):
|
||||
jobs.append((c, menu_flags + ["-include", hdr], os.path.join("l" + LV, os.path.basename(c) + ".o"), hdr))
|
||||
jobs.append((os.path.join(lg, "abs_syms.s"), [], os.path.join("l" + LV, "abs_syms.s.o"), None))
|
||||
# the executable's main-segment overrides that the level has: the level's copies
|
||||
for nm in open(os.path.join(lg, "overrides.txt")).read().split():
|
||||
c = os.path.join(HERE, "host", "overrides", nm + ".c")
|
||||
jobs.append((c, menu_flags + ["-include", hdr], os.path.join("l" + LV, "override_" + nm + ".c.o"), hdr))
|
||||
|
||||
|
||||
# compiler flags: a change rebuilds everything
|
||||
_stamp = os.path.join(obj, "flags.txt")
|
||||
_flags = " ".join(common + menu_flags)
|
||||
if not os.path.exists(_stamp) or open(_stamp).read() != _flags:
|
||||
os.makedirs(obj, exist_ok=True)
|
||||
open(_stamp, "w").write(_flags)
|
||||
flags_time = os.path.getmtime(_stamp)
|
||||
|
||||
# headers every generated file sees: a change rebuilds them all
|
||||
headers_time = max([flags_time] + [os.path.getmtime(h) for h in glob.glob(os.path.join(gen, "include", "*.h")) + glob.glob(os.path.join(HERE, "host", "include", "*.h"))])
|
||||
|
||||
|
||||
def cc(job):
|
||||
src, extra, oname, hdr = job
|
||||
o = os.path.join(obj, oname or (os.path.basename(src) + ".o"))
|
||||
if os.path.exists(o) and src.endswith(".c") and os.path.join(HERE, "host") not in src:
|
||||
t = os.path.getmtime(o)
|
||||
if t > os.path.getmtime(src) and t > headers_time and (not hdr or t > os.path.getmtime(hdr)):
|
||||
return o, None
|
||||
r = subprocess.run(zig + ["cc"] + common + extra + ["-c", src, "-o", o], capture_output=True, text=True)
|
||||
return o, (r.stderr if r.returncode else None)
|
||||
|
||||
progress("compile", 0, len(jobs))
|
||||
with ThreadPoolExecutor(a.j) as ex:
|
||||
futs = [ex.submit(cc, j) for j in jobs]
|
||||
done_n = 0
|
||||
for _ in as_completed(futs):
|
||||
done_n += 1
|
||||
if done_n % 8 == 0 or done_n == len(jobs): progress("compile", done_n, len(jobs))
|
||||
res = [f.result() for f in futs]
|
||||
progress("link", 0, 1)
|
||||
|
||||
|
||||
def objs_rsp(name):
|
||||
"""the object files as a response file (@file): Windows limits a command line to
|
||||
32767 characters, and the ~5700 objects of a full build are far more than that"""
|
||||
p = os.path.join(obj, name + ".rsp")
|
||||
with open(p, "w") as f:
|
||||
for o, _ in res: f.write('"%s"\n' % o.replace("\\", "/"))
|
||||
return "@" + p.replace("\\", "/")
|
||||
errs = [e for _, e in res if e]
|
||||
for e in errs: print(e[-3000:])
|
||||
if errs:
|
||||
sys.exit("%d files failed to compile" % len(errs))
|
||||
if a.platform == "linux":
|
||||
exe = os.path.join(out, "rac1_wrapper")
|
||||
link = zig + ["cc", "-target", target, "-o", exe, objs_rsp("link")] + ["-static", "-no-pie", "-Wl,--image-base=0x40000000"]
|
||||
r = subprocess.run(link, capture_output=True, text=True)
|
||||
if r.returncode:
|
||||
print(r.stderr[-6000:]); sys.exit("link failed")
|
||||
else:
|
||||
# the menu and runtime as a DLL at 0x40000000, plus the small launcher that owns PS2 RAM
|
||||
dll = os.path.join(out, "rac1_game.dll")
|
||||
link = zig + ["cc", "-shared", "-target", target, "-o", dll, objs_rsp("link")] + ["-Wl,--image-base=0x40000000", "-Wl,--no-dynamicbase", "-lshell32", "-lwinmm", "-lgdi32", "-luser32"]
|
||||
r = subprocess.run(link, capture_output=True, text=True)
|
||||
if r.returncode:
|
||||
print(r.stderr[-6000:]); sys.exit("link failed (dll)")
|
||||
exe = os.path.join(out, "rac1_wrapper.exe")
|
||||
L = os.path.join(HERE, "launcher")
|
||||
link = zig + ["cc", "-target", target, "-O2", "-nostdlib", "-o", exe, os.path.join(L, "launcher_win32.c"),
|
||||
os.path.join(L, "ps2ram_win32.c"), "-lkernel32", "-luser32",
|
||||
"-Wl,--image-base=0x10000", "-Wl,--entry=launcher_start@0", "-Wl,--no-dynamicbase"]
|
||||
r = subprocess.run(link, capture_output=True, text=True)
|
||||
if r.returncode:
|
||||
print(r.stderr[-6000:]); sys.exit("link failed (launcher)")
|
||||
# the library app (frontend/solana.c): build/Project Solana.exe, which finds the game in build/windows
|
||||
F = os.path.join(HERE, "frontend")
|
||||
if not os.path.isdir(F): F = None # (the game kit players download has no launcher source)
|
||||
link = None if not F else zig + ["cc", "-target", target, "-O2", "-municode", "-Wl,--subsystem,windows", "-o", os.path.join(HERE, "build", "Project Solana.exe"),
|
||||
os.path.join(F, "solana.c"), os.path.join(F, "solana.rc"), "-lgdi32", "-luser32", "-lshell32", "-lcomdlg32", "-lole32", "-lmsimg32", "-lwinhttp", "-lbcrypt", "-lwindowscodecs", "-luuid"]
|
||||
r = subprocess.run(link, capture_output=True, text=True, cwd=F) if link else None
|
||||
if r and r.returncode:
|
||||
print(r.stderr[-6000:]); sys.exit("link failed (Project Solana.exe)")
|
||||
progress("done", 1, 1)
|
||||
print("built", exe)
|
||||
@@ -0,0 +1,54 @@
|
||||
/* gs_gl_egl.c: an OpenGL 3.3 core context without a window for the GL
|
||||
* renderer's test harness (tools/gs_replay.c built with GS_GL=1, 64-bit):
|
||||
* EGL's surfaceless platform (Mesa, e.g. llvmpipe), loaded with dlopen so no
|
||||
* GL or EGL headers or link libraries are needed. */
|
||||
#include <dlfcn.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef void *EGLDisplay, *EGLContext, *EGLConfig, *EGLSurface;
|
||||
typedef int32_t EGLint;
|
||||
typedef unsigned EGLBoolean, EGLenum;
|
||||
static void *egl;
|
||||
static void *(*eglGetProcAddress_)(const char *);
|
||||
void *(*gl_proc_override)(const char *); /* the harness's stubs (REPLAY_GLNULL) */
|
||||
void *plat_gl_proc(const char *name)
|
||||
{
|
||||
if (gl_proc_override) return gl_proc_override(name);
|
||||
if (!egl) return 0;
|
||||
void *p = eglGetProcAddress_ ? eglGetProcAddress_(name) : 0;
|
||||
if (!p && egl) p = dlsym(egl, name);
|
||||
return p;
|
||||
}
|
||||
|
||||
static EGLBoolean (*egl_mc)(EGLDisplay, EGLSurface, EGLSurface, EGLContext);
|
||||
static EGLDisplay egl_d; static EGLContext egl_c;
|
||||
int egl_gl_init(void)
|
||||
{
|
||||
egl = dlopen("libEGL.so.1", RTLD_NOW);
|
||||
if (!egl) { fprintf(stderr, "no libEGL: %s\n", dlerror()); return 0; }
|
||||
eglGetProcAddress_ = (void *(*)(const char *))dlsym(egl, "eglGetProcAddress");
|
||||
EGLDisplay (*getPlatformDisplay)(EGLenum, void *, const EGLint *) = (EGLDisplay (*)(EGLenum, void *, const EGLint *))eglGetProcAddress_("eglGetPlatformDisplayEXT");
|
||||
EGLBoolean (*initialize)(EGLDisplay, EGLint *, EGLint *) = (EGLBoolean (*)(EGLDisplay, EGLint *, EGLint *))dlsym(egl, "eglInitialize");
|
||||
EGLBoolean (*bindAPI)(EGLenum) = (EGLBoolean (*)(EGLenum))dlsym(egl, "eglBindAPI");
|
||||
EGLBoolean (*chooseConfig)(EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *) = (EGLBoolean (*)(EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *))dlsym(egl, "eglChooseConfig");
|
||||
EGLContext (*createContext)(EGLDisplay, EGLConfig, EGLContext, const EGLint *) = (EGLContext (*)(EGLDisplay, EGLConfig, EGLContext, const EGLint *))dlsym(egl, "eglCreateContext");
|
||||
EGLBoolean (*makeCurrent)(EGLDisplay, EGLSurface, EGLSurface, EGLContext) = (EGLBoolean (*)(EGLDisplay, EGLSurface, EGLSurface, EGLContext))dlsym(egl, "eglMakeCurrent");
|
||||
if (!getPlatformDisplay || !initialize || !bindAPI || !chooseConfig || !createContext || !makeCurrent) { fprintf(stderr, "EGL entry points missing\n"); return 0; }
|
||||
EGLDisplay d = getPlatformDisplay(0x31DD /* EGL_PLATFORM_SURFACELESS_MESA */, 0, 0);
|
||||
EGLint ma, mi;
|
||||
if (!d || !initialize(d, &ma, &mi)) { fprintf(stderr, "eglInitialize failed\n"); return 0; }
|
||||
bindAPI(0x30A2 /* EGL_OPENGL_API */);
|
||||
EGLint ca[] = { 0x3040 /* RENDERABLE_TYPE */, 0x0008 /* OPENGL_BIT */, 0x3038 };
|
||||
EGLConfig cfg; EGLint n = 0;
|
||||
if (!chooseConfig(d, ca, &cfg, 1, &n) || n < 1) cfg = 0; /* EGL_KHR_no_config_context */
|
||||
EGLint xa[] = { 0x3098, 3, 0x30FB, 3, 0x30FD, 1 /* core */, 0x3038 };
|
||||
EGLContext c = createContext(d, cfg, 0, xa);
|
||||
if (!c) { fprintf(stderr, "eglCreateContext failed\n"); return 0; }
|
||||
if (!makeCurrent(d, 0, 0, c)) { fprintf(stderr, "eglMakeCurrent failed\n"); return 0; }
|
||||
egl_mc = makeCurrent; egl_d = d; egl_c = c;
|
||||
return 1;
|
||||
}
|
||||
/* the context current on the calling thread, or released (the GL thread takes it over) */
|
||||
void egl_gl_current(int on) { if (egl_mc && !egl_mc(egl_d, 0, 0, on ? egl_c : 0)) fprintf(stderr, "eglMakeCurrent(%d) failed\n", on); else if (getenv("REPLAY_GLDBG")) fprintf(stderr, "eglMakeCurrent(%d) ok\n", on); }
|
||||
@@ -0,0 +1,205 @@
|
||||
/* gs_replay.c: runs GS input recorded with UYA_GSREC (host/src/gs/gs.c)
|
||||
* through a build of gs.c and prints a hash of GS memory and of the shown
|
||||
* frame after every vsync, and the time the GS took. Two builds of gs.c that
|
||||
* print the same hashes drew the same pixels.
|
||||
*
|
||||
* build (32-bit like the game; GS_C is the gs.c to test):
|
||||
* zig cc -target x86-linux-musl -O2 -march=nehalem -mfpmath=sse -malign-double -fno-strict-aliasing -fwrapv \
|
||||
* -I host/include -I host/src/gs -DGS_C='"../host/src/gs/gs.c"' tools/gs_replay.c host/src/gs/gs_gl.c -o gs_replay -static -lpthread
|
||||
* (a gs.c elsewhere: an absolute path in GS_C and -I host/src/gs)
|
||||
* run: gs_replay rec.bin [frames_dir] (UYA_GS_THREADS=n: drawing threads, default 0;
|
||||
* REPLAY_QUIET=1: no GS log; REPLAY_PROF=file: a program counter histogram, as UYA_PROF)
|
||||
*
|
||||
* The GL renderer (host/src/gs/gs_gl.c) in a 64-bit build with an EGL
|
||||
* context without a window (Mesa llvmpipe), frames read back from the GPU:
|
||||
* gcc -O2 -DGS_GL -I host/include -I host/src/gs -DGS_C='"../host/src/gs/gs.c"' tools/gs_replay.c \
|
||||
* host/src/gs/gs_gl.c tools/gs_gl_egl.c -o gs_replay_gl -ldl -lpthread -lm
|
||||
* REPLAY_SOFT=1: the software GS in the same binary; REPLAY_GLNULL=1: GL calls do nothing (the
|
||||
* CPU cost of the front end and the batching alone); REPLAY_VRAMSYNC=1: GPU targets read back into
|
||||
* GS memory at each vsync, so the vram hash compares */
|
||||
#define _GNU_SOURCE
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <signal.h>
|
||||
#include <ucontext.h>
|
||||
#include <sys/time.h>
|
||||
#include GS_C
|
||||
|
||||
/* REPLAY_PROF=file: a histogram of the program counter, 1 ms of CPU time apart ("eip tid count") */
|
||||
static u32 prof_eip[1 << 16], prof_n[1 << 16];
|
||||
static void on_prof(int sig, siginfo_t *si, void *ctxv)
|
||||
{
|
||||
u32 eip = (u32)((ucontext_t *)ctxv)->uc_mcontext.gregs[
|
||||
#ifdef __x86_64__
|
||||
REG_RIP
|
||||
#else
|
||||
REG_EIP
|
||||
#endif
|
||||
], h = (eip * 2654435761u) >> 16;
|
||||
(void)sig; (void)si;
|
||||
for (int k = 0; k < 64; k++) { u32 j = (h + k) & 0xFFFF; if (prof_n[j] && prof_eip[j] != eip) continue; prof_eip[j] = eip; prof_n[j]++; return; }
|
||||
}
|
||||
static void prof_dump(void)
|
||||
{
|
||||
FILE *f = fopen(getenv("REPLAY_PROF"), "w");
|
||||
if (f) { for (int j = 0; j < 1 << 16; j++) if (prof_n[j]) fprintf(f, "%08x 0 %u\n", prof_eip[j], prof_n[j]); fclose(f); }
|
||||
}
|
||||
|
||||
#ifdef GS_GL
|
||||
#include "gs_gl_api.h"
|
||||
int egl_gl_init(void);
|
||||
int plat_gl_active;
|
||||
static int gl_null;
|
||||
int plat_gl_init(void) { return gl_null ? 1 : egl_gl_init(); }
|
||||
void egl_gl_current(int on);
|
||||
void plat_gl_current(int on) { if (!gl_null) egl_gl_current(on); }
|
||||
void plat_gl_swap(void) {}
|
||||
void plat_gl_size(int *w, int *h) { *w = 640; *h = 480; }
|
||||
/* REPLAY_GLNULL: every GL call returns 0 (a few return what initialisation needs) */
|
||||
static long gl_nop(void) { return 0; }
|
||||
static void GLAPIENTRY gl_getiv(GLuint a, GLenum b, GLint *v) { (void)a; (void)b; *v = 1; }
|
||||
static char gl_mapbuf[8 << 20];
|
||||
static void *GLAPIENTRY gl_map(GLenum a, GLintptr b, GLsizeiptr c, GLbitfield d) { (void)a; (void)b; (void)c; (void)d; return gl_mapbuf; }
|
||||
static GLuint gl_names;
|
||||
static void GLAPIENTRY gl_gen(GLsizei n, GLuint *o) { for (int i = 0; i < n; i++) o[i] = ++gl_names; }
|
||||
static GLuint GLAPIENTRY gl_create(void) { return ++gl_names; }
|
||||
static GLuint GLAPIENTRY gl_create1(GLenum e) { (void)e; return ++gl_names; }
|
||||
void *plat_gl_proc(const char *name);
|
||||
static void *null_proc(const char *n)
|
||||
{
|
||||
if (!strcmp(n, "glGetShaderiv") || !strcmp(n, "glGetProgramiv")) return (void *)gl_getiv;
|
||||
if (!strcmp(n, "glMapBufferRange")) return (void *)gl_map;
|
||||
if (!strcmp(n, "glGenTextures") || !strcmp(n, "glGenBuffers") || !strcmp(n, "glGenVertexArrays") || !strcmp(n, "glGenFramebuffers")) return (void *)gl_gen;
|
||||
if (!strcmp(n, "glCreateProgram")) return (void *)gl_create;
|
||||
if (!strcmp(n, "glCreateShader")) return (void *)gl_create1;
|
||||
return (void *)gl_nop;
|
||||
}
|
||||
#else
|
||||
int plat_gl_active; /* (no GL in this build) */
|
||||
int plat_gl_init(void) { return 0; }
|
||||
void plat_gl_current(int on) { (void)on; }
|
||||
void plat_gl_swap(void) {}
|
||||
void plat_gl_size(int *w, int *h) { *w = 640; *h = 480; }
|
||||
void *plat_gl_proc(const char *name) { (void)name; return 0; }
|
||||
#endif
|
||||
static uint32_t replay_vs;
|
||||
uint32_t uya_frame_count(void) { return replay_vs; }
|
||||
u32 vu_nonfinite;
|
||||
const char *plat_exe_dir(void) { return "."; }
|
||||
void uya_log(const char *fmt, ...)
|
||||
{
|
||||
if (getenv("REPLAY_QUIET")) return;
|
||||
va_list ap; va_start(ap, fmt); fprintf(stderr, "[gs] "); vfprintf(stderr, fmt, ap); fputc('\n', stderr); va_end(ap);
|
||||
}
|
||||
int plat_present(const uint32_t *px, int w, int h) { (void)px; (void)w; (void)h; return 1; }
|
||||
unsigned long long nsends, uya_t_vu;
|
||||
void uya_vu_dump_kick(void) {}
|
||||
void uya_vif1_dump_hist(void) {}
|
||||
|
||||
static uint32_t hash32(const void *p, size_t n) /* FNV-1a over words */
|
||||
{
|
||||
const uint32_t *w = p; uint32_t h = 2166136261u;
|
||||
for (size_t i = 0; i < n / 4; i++) h = (h ^ w[i]) * 16777619u;
|
||||
return h;
|
||||
}
|
||||
static double now_s(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return t.tv_sec + t.tv_nsec / 1e9; }
|
||||
static double cpu_s(void) { struct timespec t; clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t); return t.tv_sec + t.tv_nsec / 1e9; }
|
||||
static void rd(FILE *f, void *p, size_t n) { if (fread(p, 1, n, f) != n) { fprintf(stderr, "short read\n"); exit(1); } }
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2) { fprintf(stderr, "usage: gs_replay rec.bin [frames_dir]\n"); return 1; }
|
||||
if (!getenv("UYA_GS_THREADS")) setenv("UYA_GS_THREADS", "0", 1);
|
||||
if (getenv("REPLAY_PROF")) {
|
||||
struct sigaction sa; memset(&sa, 0, sizeof sa); sa.sa_flags = SA_SIGINFO | SA_RESTART; sa.sa_sigaction = on_prof; sigaction(SIGPROF, &sa, 0);
|
||||
struct itimerval it = { { 0, 1000 }, { 0, 1000 } }; setitimer(ITIMER_PROF, &it, 0); atexit(prof_dump);
|
||||
}
|
||||
setenv("UYA_MTGS", "0", 1);
|
||||
#ifdef GS_GL
|
||||
if (!getenv("REPLAY_SOFT")) setenv("UYA_RENDERER", "gl", 1);
|
||||
gl_null = getenv("REPLAY_GLNULL") != 0;
|
||||
if (gl_null) { extern void *(*gl_proc_override)(const char *); gl_proc_override = null_proc; } /* the loader hands out the stubs */
|
||||
#endif
|
||||
FILE *f = fopen(argv[1], "rb");
|
||||
if (!f) { perror(argv[1]); return 1; }
|
||||
uya_gs_init();
|
||||
u32 m; rd(f, &m, 4);
|
||||
if (m != 0x52534755u) { fprintf(stderr, "not a GS recording\n"); return 1; }
|
||||
#define X(fl) { u32 n_; rd(f, &n_, 4); if (n_ != sizeof fl) { fprintf(stderr, "field " #fl " size %u, expected %u\n", n_, (u32)sizeof fl); return 1; } rd(f, &fl, sizeof fl); }
|
||||
GS_SNAP_FIELDS(X)
|
||||
#undef X
|
||||
rd(f, vram, 4 << 20);
|
||||
static u32 buf[(0x10000 + 16) * 4];
|
||||
static u32 *big; static size_t bign;
|
||||
double t = 0, t0 = now_s(), c = 0;
|
||||
u32 k;
|
||||
while (fread(&k, 4, 1, f) == 1) {
|
||||
if (k == 1) {
|
||||
u32 h[2]; rd(f, h, 8);
|
||||
u32 *d = buf;
|
||||
if (h[1] > 0x10000) { if (bign < h[1] * 16) { free(big); big = malloc(h[1] * 16); bign = h[1] * 16; } d = big; }
|
||||
rd(f, d, h[1] * 16);
|
||||
double a = now_s(), ac = cpu_s();
|
||||
uya_gs_src = path_name[h[0] < 3 ? h[0] : 2];
|
||||
uya_gs_gif(d, h[1]);
|
||||
t += now_s() - a; c += cpu_s() - ac;
|
||||
} else if (k == 2) {
|
||||
u64 r[6]; rd(f, r, 48);
|
||||
double a = now_s(), ac = cpu_s();
|
||||
u32 w = 0, hh = 0;
|
||||
if (getenv("REPLAY_DISPFBP")) r[2] = (r[2] & ~0x1FFull) | ((u64)strtol(getenv("REPLAY_DISPFBP"), 0, 16) / 32); /* testing: show another buffer */
|
||||
gs_drain();
|
||||
int ok;
|
||||
#ifdef GS_GL
|
||||
if (gsgl_on) {
|
||||
gs_disp_t dd;
|
||||
ok = gs_disp(r[0], r[1], r[2], r[3], r[4], r[5], &dd);
|
||||
if (ok && !gl_null) ok = gsgl_capture(&dd, frame_px);
|
||||
else gsgl_flush();
|
||||
w = dd.w; hh = dd.h;
|
||||
if (getenv("REPLAY_VRAMSYNC")) gsgl_cpu_read(0, 0x4000);
|
||||
if (ok && !gl_null && getenv("REPLAY_FULL") && argc > 2) { /* the upscaled picture too: full_NNNNN.ppm */
|
||||
int gsgl_capture_full(const gs_disp_t *d, u32 *out, int *ow, int *oh);
|
||||
static u32 *big; int fw, fh;
|
||||
if (!big) big = malloc((size_t)4096 * 4096 * 4);
|
||||
if (gsgl_capture_full(&dd, big, &fw, &fh)) {
|
||||
char p[512]; snprintf(p, sizeof p, "%s/full_%05u.ppm", argv[2], replay_vs + 1);
|
||||
FILE *o = fopen(p, "wb");
|
||||
if (o) { fprintf(o, "P6 %d %d 255\n", fw, fh);
|
||||
for (int y = 0; y < fh; y++) for (int x = 0; x < fw; x++) { u32 c = big[(size_t)y * fw + x]; fputc(c & 255, o); fputc((c >> 8) & 255, o); fputc((c >> 16) & 255, o); }
|
||||
fclose(o); }
|
||||
}
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
ok = gs_capture(r[0], r[1], r[2], r[3], r[4], r[5], frame_px, &w, &hh);
|
||||
t += now_s() - a; c += cpu_s() - ac;
|
||||
replay_vs++;
|
||||
printf("vsync %u vram %08X frame %08X\n", replay_vs, hash32(vram, 4 << 20), ok ? hash32(frame_px, (size_t)w * hh * 4) : 0);
|
||||
if (argc > 2 && ok) {
|
||||
char p[512]; snprintf(p, sizeof p, "%s/r_%05u.ppm", argv[2], replay_vs);
|
||||
FILE *o = fopen(p, "wb");
|
||||
if (o) { fprintf(o, "P6 %u %u 255\n", w, hh);
|
||||
for (u32 i = 0; i < w * hh; i++) { u32 c = frame_px[i]; fputc((c >> 16) & 255, o); fputc((c >> 8) & 255, o); fputc(c & 255, o); }
|
||||
fclose(o); }
|
||||
}
|
||||
} else if (k == 3) {
|
||||
u32 h[4]; rd(f, h, 16);
|
||||
u32 *px = malloc((size_t)h[2] * h[3] * 4); rd(f, px, (size_t)h[2] * h[3] * 4);
|
||||
double a = now_s();
|
||||
uya_gs_write_ct32(h[0], h[1], (int)h[2], (int)h[3], px);
|
||||
t += now_s() - a;
|
||||
free(px);
|
||||
} else { fprintf(stderr, "bad record %u\n", k); return 1; }
|
||||
}
|
||||
#ifdef GS_GL
|
||||
if (gsgl_on) gsgl_stats((int)replay_vs);
|
||||
#endif
|
||||
fprintf(stderr, "%u vsyncs: GS time %.3f s (%.2f ms/vsync), CPU %.2f ms/vsync, wall %.3f s\n", replay_vs, t, replay_vs ? 1000 * t / replay_vs : 0,
|
||||
replay_vs ? 1000 * c / replay_vs : 0, now_s() - t0);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
#!/usr/bin/env python3
|
||||
"""vu_refcheck.py DECOMP_REPO IMAGE VU [vucap_before.bin vucap_after.bin]
|
||||
|
||||
Testing aid for tools/vu_recomp.py: a plain interpreter of VU micro code that
|
||||
replays a state the running wrapper captured (UYA_VUCAPTURE=image,n, see
|
||||
host/src/gfx/vu.c) and compares its result with what the translated C
|
||||
produced: registers, flags, Q and data memory. It works out stalls at run
|
||||
time (the translator does it statically), so it also checks that model. The
|
||||
MAC/status flags are only compared for images that read them (the
|
||||
translation skips them otherwise). Needs numpy.
|
||||
"""
|
||||
import os, struct, sys
|
||||
import numpy as np
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from vu_isa import decode_pair, XYZW
|
||||
|
||||
f32 = np.float32
|
||||
np.seterr(all="ignore")
|
||||
|
||||
def load_images():
|
||||
d = open(os.path.join(sys.argv[1], "baserom", "SCES_509.16"), "rb").read()
|
||||
e_shoff, = struct.unpack_from("<I", d, 0x20); shentsize, shnum, shstrndx = struct.unpack_from("<HHH", d, 0x2E)
|
||||
secs = [struct.unpack_from("<10I", d, e_shoff + i * shentsize) for i in range(shnum)]
|
||||
so = secs[shstrndx][4]
|
||||
sizes = {}
|
||||
for s in secs:
|
||||
n = d[so + s[0]:d.index(b"\0", so + s[0])].decode()
|
||||
if n.startswith(".DVP.overlay"): sizes[n] = s[5]
|
||||
if n == ".DVP.ovlytab": tab = d[s[4]:s[4] + s[5]]
|
||||
if n == ".DVP.ovlystrtab": st = d[s[4]:s[4] + s[5]]
|
||||
imgs = {}
|
||||
for i in range(0, len(tab) // 12 * 12, 12):
|
||||
no, lma, vma = struct.unpack_from("<3I", tab, i)
|
||||
n = st[no:st.index(b"\0", no)].decode()
|
||||
g = n.split(".")[5]
|
||||
imgs.setdefault(g, {})
|
||||
b = d[lma - 0x100080 + 0x1000: lma - 0x100080 + 0x1000 + sizes[n]]
|
||||
for k in range(0, len(b), 8):
|
||||
imgs[g][vma + k] = struct.unpack_from("<II", b, k)
|
||||
return imgs
|
||||
|
||||
FLAGREAD = {"fcand", "fcor", "fceq", "fcget", "fsand", "fsor", "fseq", "fmand", "fmor", "fmeq"}
|
||||
def vi_written(lw):
|
||||
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
|
||||
|
||||
SZ = 748 # sizeof(RcVu0), host/include/rc.h (keep in step with the struct)
|
||||
def parse(path, vu):
|
||||
b = open(path, "rb").read()
|
||||
pc, = struct.unpack_from("<I", b, 0)
|
||||
st = b[4:4 + SZ]
|
||||
msz = 0x4000 if vu else 0x1000
|
||||
mem = bytearray(b[4 + SZ:4 + SZ + msz])
|
||||
top, itop = struct.unpack_from("<2I", b, 4 + SZ + msz) if len(b) >= 4 + SZ + msz + 8 else (0, 0)
|
||||
vf = np.frombuffer(st[0:512], dtype=np.uint32).reshape(32, 4).copy()
|
||||
acc = np.frombuffer(st[512:528], dtype=np.uint32).copy()
|
||||
q, i, p, r = struct.unpack_from("<4I", st, 528)
|
||||
vi = list(struct.unpack_from("<16I", st, 544))
|
||||
clip, status, mac = struct.unpack_from("<3I", st, 608)
|
||||
return dict(pc=pc, vf=vf, acc=acc, q=q, i=i, vi=vi, clip=clip, status=status, mac=mac, mem=mem, top=top, itop=itop)
|
||||
|
||||
def fl(u): return np.array([u], dtype=np.uint32).view(np.float32)[0]
|
||||
def bits(x): return int(np.array([x], dtype=np.float32).view(np.uint32)[0])
|
||||
|
||||
class VU:
|
||||
def __init__(s, st, code, vu):
|
||||
s.vf = st["vf"]; s.acc = st["acc"]; s.vi = st["vi"]; s.q = st["q"]; s.i = st["i"]
|
||||
s.clip = st["clip"]; s.status = st["status"]; s.mac = st["mac"]; s.mem = st["mem"]
|
||||
s.code = code; s.vu = vu; s.qmask = 0x3FF if vu else 0xFF
|
||||
s.cyc = 0; s.ready = {} # (reg, comp) -> cycle
|
||||
s.fpipe = [] # (rdy, mac, status)
|
||||
s.cpipe = []
|
||||
s.cl_int = s.clip; s.st_int = s.status
|
||||
s.qp = None # (rdy, val)
|
||||
s.kicks = []
|
||||
s.top, s.itop = st["top"], st["itop"]
|
||||
s.vhist = []
|
||||
s.targets = set()
|
||||
for pc_, w_ in code.items():
|
||||
l_ = decode_pair(*w_)[1]
|
||||
if l_ is not None and l_.name in ("b", "bal", "ibeq", "ibne", "ibltz", "ibgtz", "iblez", "ibgez"):
|
||||
s.targets.add((pc_ + 8 + l_.imm * 8) & (0x3FF8 if vu else 0xFF8))
|
||||
def F(s, r, c): return fl(int(s.vf[r][c]))
|
||||
def setF(s, r, c, x):
|
||||
if r: s.vf[r][c] = bits(x)
|
||||
def memw(s, qa, c): o = ((qa & s.qmask) << 4) + 4 * c; return struct.unpack_from("<I", s.mem, o)[0]
|
||||
def setmem(s, qa, c, v): o = ((qa & s.qmask) << 4) + 4 * c; struct.pack_into("<I", s.mem, o, v & 0xFFFFFFFF)
|
||||
def flsync(s):
|
||||
while s.fpipe and s.fpipe[0][0] <= s.cyc:
|
||||
_, s.mac, s.status = s.fpipe.pop(0)
|
||||
while s.cpipe and s.cpipe[0][0] <= s.cyc:
|
||||
_, s.clip = s.cpipe.pop(0)
|
||||
if s.qp and s.qp[0] <= s.cyc:
|
||||
s.q = s.qp[1]; s.qp = None
|
||||
def setvi(s, r, v):
|
||||
if r & 15: s.vi[r & 15] = v & 0xFFFF
|
||||
def run(s, pc, maxsteps=2000000):
|
||||
bpending = None; ending = 0
|
||||
steps = 0
|
||||
while True:
|
||||
steps += 1
|
||||
if steps > maxsteps: raise Exception("too many steps")
|
||||
lo, hi = s.code[pc]
|
||||
u, l, flg, imm = decode_pair(lo, hi)
|
||||
# stalls
|
||||
reads = s.reads(u, l)
|
||||
t = s.cyc + 1
|
||||
need = max([s.ready.get(x, 0) for x in reads] + [0])
|
||||
t = max(t, need)
|
||||
if l is not None and l.name in ("waitq", "div", "sqrt", "rsqrt") and s.qp:
|
||||
t = max(t, s.qp[0])
|
||||
stalled = t > s.cyc + 1
|
||||
s.cyc = t
|
||||
s.flsync()
|
||||
# upper
|
||||
ures = s.upper(u)
|
||||
# lower
|
||||
nb = None
|
||||
before_vi = list(s.vi)
|
||||
if imm is not None:
|
||||
s.i = imm
|
||||
elif l is not None:
|
||||
# branch VI delay (see tools/vu_recomp.py): the branch reads the value
|
||||
# from before a run of writes to its register right before it
|
||||
patch = {}
|
||||
if l.name in ("ibeq", "ibne", "ibltz", "ibgtz", "iblez", "ibgez", "jr", "jalr") and not stalled and pc not in s.targets:
|
||||
regs = [l.is_ & 15] + ([l.it & 15] if l.name in ("ibeq", "ibne") else [])
|
||||
for x in regs:
|
||||
if not x: continue
|
||||
h = s.vhist
|
||||
if not h or h[-1][0] != x or h[-1][2]: continue
|
||||
k = len(h) - 1
|
||||
while k > 0 and len(h) - k < 4 and h[k - 1][0] == x and not h[k - 1][2] and not h[k][3] and not h[k][4]:
|
||||
k -= 1
|
||||
patch[x] = h[k][1]
|
||||
break
|
||||
real = {x: s.vi[x] for x in patch}
|
||||
for x, val in patch.items(): s.vi[x] = val
|
||||
nb = s.lower(l, pc)
|
||||
for x, val in patch.items():
|
||||
if s.vi[x] == val: s.vi[x] = real[x]
|
||||
# history of VI writes: (reg, value before, reads flags, stalled, branch target)
|
||||
if l is not None and imm is None:
|
||||
wr = vi_written(l)
|
||||
s.vhist.append((wr, before_vi[wr] if wr else 0, l.name in FLAGREAD, stalled, pc in s.targets))
|
||||
if len(s.vhist) > 8: s.vhist.pop(0)
|
||||
else:
|
||||
s.vhist.append((0, 0, False, stalled, pc in s.targets))
|
||||
if len(s.vhist) > 8: s.vhist.pop(0)
|
||||
# commit upper
|
||||
if ures: ures()
|
||||
for x in s.writes(u, l):
|
||||
s.ready[x] = s.cyc + 4
|
||||
# control
|
||||
if ending:
|
||||
return pc + 8
|
||||
if bpending is not None:
|
||||
tgt = bpending; bpending = None
|
||||
if flg["E"]: ending = 1
|
||||
pc = tgt
|
||||
continue
|
||||
if flg["E"]:
|
||||
ending = 1
|
||||
if nb is not None:
|
||||
bpending = nb
|
||||
pc = pc + 8
|
||||
def reads(s, u, l):
|
||||
r = []
|
||||
k = u.kind
|
||||
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))
|
||||
elif k in ("opmula", "opmsub"):
|
||||
for c in range(3): r += [(u.fs, c), (u.ft, c)]
|
||||
elif k in ("itof", "ftoi", "abs"): r += [(u.fs, c) for c in u.comps]
|
||||
elif k == "clip": r += [(u.fs, 0), (u.fs, 1), (u.fs, 2), (u.ft, 3)]
|
||||
if l is not None:
|
||||
n = l.name
|
||||
if n in ("sq", "sqi", "sqd", "move"): r += [(l.fs, c) for c in l.comps]
|
||||
elif n == "mr32": r += [(l.fs, (c + 1) & 3) for c in l.comps]
|
||||
elif n == "mtir": r.append((l.fs, l.fsf))
|
||||
elif n == "div" or n == "rsqrt": r += [(l.fs, l.fsf), (l.ft, l.ftf)]
|
||||
elif n == "sqrt": r.append((l.ft, l.ftf))
|
||||
return [x for x in r if x[0]]
|
||||
def writes(s, u, l):
|
||||
w = []
|
||||
k = u.kind
|
||||
if k == "arith" and not u.acc: w += [(u.fd, c) for c in u.comps]
|
||||
elif k == "opmsub": w += [(u.fd, c) for c in u.comps if c < 3]
|
||||
elif k in ("itof", "ftoi", "abs"): w += [(u.ft, c) for c in u.comps]
|
||||
if l is not None and l.name in ("lq", "lqi", "lqd", "move", "mr32", "mfir"):
|
||||
w += [(l.ft, c) for c in l.comps]
|
||||
return [x for x in w if x[0]]
|
||||
def upper(s, u):
|
||||
k = u.kind
|
||||
if k == "nop": return None
|
||||
if k == "arith":
|
||||
base = u.base if u.acc else u.name
|
||||
def B(c):
|
||||
if u.bc in ("x","y","z","w"): return s.F(u.ft, XYZW.index(u.bc))
|
||||
if u.bc == "q": return fl(s.q)
|
||||
if u.bc == "i": return fl(s.i)
|
||||
return s.F(u.ft, c)
|
||||
res = {}
|
||||
for c in u.comps:
|
||||
a, b = s.F(u.fs, c), B(c)
|
||||
accv = fl(int(s.acc[c]))
|
||||
if base == "add": x = f32(a + b)
|
||||
elif base == "sub": x = f32(a - b)
|
||||
elif base == "mul": x = f32(a * b)
|
||||
elif base == "madd": x = f32(accv + f32(a * b))
|
||||
elif base == "msub": x = f32(accv - f32(a * b))
|
||||
elif base == "max": x = np.fmax(a, b)
|
||||
elif base == "mini": x = np.fmin(a, b)
|
||||
res[c] = x
|
||||
def commit():
|
||||
for c, x in res.items():
|
||||
if u.acc: s.acc[c] = bits(x)
|
||||
elif u.fd: s.vf[u.fd][c] = bits(x)
|
||||
if base in ("add", "sub", "mul", "madd", "msub"):
|
||||
s.push_flags(res)
|
||||
return commit
|
||||
if k in ("opmula", "opmsub"):
|
||||
ex = {0: (1, 2), 1: (2, 0), 2: (0, 1)}
|
||||
res = {}
|
||||
for c in [c for c in u.comps if c < 3]:
|
||||
a, b = ex[c]
|
||||
pr = f32(s.F(u.fs, a) * s.F(u.ft, b))
|
||||
res[c] = pr if k == "opmula" else f32(fl(int(s.acc[c])) - pr)
|
||||
def commit():
|
||||
for c, x in res.items():
|
||||
if k == "opmula": s.acc[c] = bits(x)
|
||||
elif u.fd: s.vf[u.fd][c] = bits(x)
|
||||
s.push_flags(res)
|
||||
return commit
|
||||
if k == "itof":
|
||||
res = {c: bits(f32(np.int32(np.uint32(s.vf[u.fs][c]).view(np.int32)) / f32(1 << u.shift))) for c in u.comps}
|
||||
def commit():
|
||||
for c, x in res.items():
|
||||
if u.ft: s.vf[u.ft][c] = x
|
||||
return commit
|
||||
if k == "ftoi":
|
||||
res = {}
|
||||
for c in u.comps:
|
||||
x = float(s.F(u.fs, c)) * (1 << u.shift)
|
||||
x = f32(x)
|
||||
if x != x: v = 0
|
||||
elif x >= 2147483647.0: v = 0x7FFFFFFF
|
||||
elif x <= -2147483648.0: v = -0x80000000
|
||||
else: v = int(x)
|
||||
res[c] = v & 0xFFFFFFFF
|
||||
def commit():
|
||||
for c, x in res.items():
|
||||
if u.ft: s.vf[u.ft][c] = x
|
||||
return commit
|
||||
if k == "abs":
|
||||
res = {c: int(s.vf[u.fs][c]) & 0x7FFFFFFF for c in u.comps}
|
||||
def commit():
|
||||
for c, x in res.items():
|
||||
if u.ft: s.vf[u.ft][c] = x
|
||||
return commit
|
||||
if k == "clip":
|
||||
w = abs(s.F(u.ft, 3)); x, y, z = s.F(u.fs, 0), s.F(u.fs, 1), s.F(u.fs, 2)
|
||||
f = (x > w) | ((x < -w) << 1) | ((y > w) << 2) | ((y < -w) << 3) | ((z > w) << 4) | ((z < -w) << 5)
|
||||
def commit():
|
||||
s.cl_int = ((s.cl_int << 6) | int(f)) & 0xFFFFFF
|
||||
s.cpipe.append((s.cyc + 4, s.cl_int))
|
||||
return commit
|
||||
raise Exception("upper " + u.name)
|
||||
def push_flags(s, res):
|
||||
mac = 0
|
||||
for c, x in res.items():
|
||||
b = bits(x)
|
||||
if (b & 0x7FFFFFFF) == 0: mac |= 8 >> c
|
||||
if b >> 31: mac |= 0x80 >> c
|
||||
st = (s.st_int & 0xFC0) | (1 if mac & 0xF else 0) | (2 if mac & 0xF0 else 0)
|
||||
st |= (st & 3) << 6
|
||||
s.st_int = st
|
||||
s.fpipe.append((s.cyc + 4, mac, st))
|
||||
def lower(s, l, pc):
|
||||
n = l.name; vi = s.vi
|
||||
V = lambda r: vi[r & 15] if r & 15 else 0
|
||||
if n == "move":
|
||||
vals = [int(s.vf[l.fs][c]) for c in range(4)]
|
||||
for c in l.comps:
|
||||
if l.ft: s.vf[l.ft][c] = vals[c]
|
||||
elif n == "mr32":
|
||||
vals = [int(s.vf[l.fs][(c + 1) & 3]) for c in range(4)]
|
||||
for c in l.comps:
|
||||
if l.ft: s.vf[l.ft][c] = vals[c]
|
||||
elif n in ("lq", "lqi", "lqd"):
|
||||
if n == "lqd": s.setvi(l.is_, V(l.is_) - 1)
|
||||
a = V(l.is_) + (l.imm if n == "lq" else 0)
|
||||
for c in l.comps:
|
||||
if l.ft: s.vf[l.ft][c] = s.memw(a, c)
|
||||
if n == "lqi": s.setvi(l.is_, V(l.is_) + 1)
|
||||
elif n in ("sq", "sqi", "sqd"):
|
||||
if n == "sqd": s.setvi(l.it, V(l.it) - 1)
|
||||
a = V(l.it) + (l.imm if n == "sq" else 0)
|
||||
for c in l.comps: s.setmem(a, c, int(s.vf[l.fs][c]))
|
||||
if n == "sqi": s.setvi(l.it, V(l.it) + 1)
|
||||
elif n in ("ilw", "ilwr"):
|
||||
a = V(l.is_) + (l.imm if n == "ilw" else 0)
|
||||
s.setvi(l.it, s.memw(a, l.comps[0]))
|
||||
elif n in ("isw", "iswr"):
|
||||
a = V(l.is_) + (l.imm if n == "isw" else 0)
|
||||
for c in l.comps: s.setmem(a, c, V(l.it))
|
||||
elif n == "iadd": s.setvi(l.id_, V(l.is_) + V(l.it))
|
||||
elif n == "isub": s.setvi(l.id_, V(l.is_) - V(l.it))
|
||||
elif n == "iand": s.setvi(l.id_, V(l.is_) & V(l.it))
|
||||
elif n == "ior": s.setvi(l.id_, V(l.is_) | V(l.it))
|
||||
elif n == "iaddi": s.setvi(l.it, V(l.is_) + l.imm)
|
||||
elif n == "iaddiu": s.setvi(l.it, V(l.is_) + l.imm)
|
||||
elif n == "isubiu": s.setvi(l.it, V(l.is_) - l.imm)
|
||||
elif n == "mtir": s.setvi(l.it, int(s.vf[l.fs][l.fsf]))
|
||||
elif n == "mfir":
|
||||
v = V(l.is_); v = v - 0x10000 if v & 0x8000 else v
|
||||
for c in l.comps:
|
||||
if l.ft: s.vf[l.ft][c] = v & 0xFFFFFFFF
|
||||
elif n in ("div", "sqrt", "rsqrt"):
|
||||
if s.qp: s.q = s.qp[1]; s.qp = None
|
||||
if n == "div":
|
||||
a, b = s.F(l.fs, l.fsf), s.F(l.ft, l.ftf)
|
||||
v = bits(f32(a / b)) if b != 0 else (0xFF7FFFFF if (bits(a) ^ bits(b)) & 0x80000000 else 0x7F7FFFFF)
|
||||
s.qp = (s.cyc + 7, v)
|
||||
elif n == "sqrt":
|
||||
s.qp = (s.cyc + 7, bits(f32(np.sqrt(abs(s.F(l.ft, l.ftf))))))
|
||||
else:
|
||||
a, b = s.F(l.fs, l.fsf), f32(np.sqrt(abs(s.F(l.ft, l.ftf))))
|
||||
v = bits(f32(a / b)) if b != 0 else (0xFF7FFFFF if bits(a) & 0x80000000 else 0x7F7FFFFF)
|
||||
s.qp = (s.cyc + 13, v)
|
||||
elif n == "waitq": pass
|
||||
elif n == "fcand": s.setvi(1, 1 if (s.clip & l.imm) else 0)
|
||||
elif n == "fcor": s.setvi(1, 1 if ((s.clip | l.imm) & 0xFFFFFF) == 0xFFFFFF else 0)
|
||||
elif n == "fceq": s.setvi(1, 1 if (s.clip & 0xFFFFFF) == l.imm else 0)
|
||||
elif n == "fcset":
|
||||
s.cpipe = []; s.clip = s.cl_int = l.imm
|
||||
elif n == "fcget": s.setvi(l.it, s.clip & 0xFFF)
|
||||
elif n == "fsand": s.setvi(l.it, s.status & l.imm)
|
||||
elif n == "fmand": s.setvi(l.it, V(l.is_) & s.mac)
|
||||
elif n == "fmor": s.setvi(l.it, V(l.is_) | s.mac)
|
||||
elif n == "xgkick": s.kicks.append(V(l.is_) & 0x3FF)
|
||||
elif n == "xtop": s.setvi(l.it, s.top)
|
||||
elif n == "xitop": s.setvi(l.it, s.itop)
|
||||
elif n == "b": return (pc + 8 + l.imm * 8) & (0x3FF8 if s.vu else 0xFF8)
|
||||
elif n == "bal": s.setvi(l.it, (pc + 16) // 8); return (pc + 8 + l.imm * 8)
|
||||
elif n == "jr": return (V(l.is_) * 8)
|
||||
elif n == "jalr":
|
||||
t = V(l.is_) * 8; s.setvi(l.it, (pc + 16) // 8); return t
|
||||
elif n in ("ibeq", "ibne", "ibltz", "ibgtz", "iblez", "ibgez"):
|
||||
a, b = V(l.it), V(l.is_)
|
||||
sb = b - 0x10000 if b & 0x8000 else b
|
||||
c = {"ibeq": a == b, "ibne": a != b, "ibltz": sb < 0, "ibgtz": sb > 0, "iblez": sb <= 0, "ibgez": sb >= 0}[n]
|
||||
if c: return pc + 8 + l.imm * 8
|
||||
else:
|
||||
raise Exception("lower " + n)
|
||||
return None
|
||||
|
||||
def main():
|
||||
g, vu = sys.argv[2], int(sys.argv[3])
|
||||
bp = sys.argv[4] if len(sys.argv) > 4 else "vucap_before.bin"
|
||||
ap = sys.argv[5] if len(sys.argv) > 5 else "vucap_after.bin"
|
||||
code = load_images()[g]
|
||||
reads_flags = any(decode_pair(*w)[1] is not None and decode_pair(*w)[1].name in
|
||||
("fsand", "fsor", "fseq", "fmand", "fmor", "fmeq") for w in code.values())
|
||||
st = parse(bp, vu); af = parse(ap, vu)
|
||||
m = VU(st, code, vu)
|
||||
m.run(st["pc"])
|
||||
while m.fpipe: _, m.mac, m.status = m.fpipe.pop(0)
|
||||
while m.cpipe: _, m.clip = m.cpipe.pop(0)
|
||||
if m.qp: m.q = m.qp[1]
|
||||
diffs = 0
|
||||
for r in range(32):
|
||||
for c in range(4):
|
||||
if int(m.vf[r][c]) != int(af["vf"][r][c]):
|
||||
diffs += 1
|
||||
if diffs < 30: print("vf%d.%s ref %08X C %08X" % (r, XYZW[c], int(m.vf[r][c]), int(af["vf"][r][c])))
|
||||
for r in range(16):
|
||||
if m.vi[r] != af["vi"][r]: diffs += 1; print("vi%d ref %04X C %04X" % (r, m.vi[r], af["vi"][r]))
|
||||
for c in range(4):
|
||||
if int(m.acc[c]) != int(af["acc"][c]): diffs += 1; print("acc.%s ref %08X C %08X" % (XYZW[c], int(m.acc[c]), int(af["acc"][c])))
|
||||
for nm in ("q", "clip") + (("status", "mac") if reads_flags else ()):
|
||||
if getattr(m, nm) != af[nm]: diffs += 1; print("%s ref %08X C %08X" % (nm, getattr(m, nm), af[nm]))
|
||||
md = [k for k in range(0, len(m.mem), 4) if m.mem[k:k+4] != af["mem"][k:k+4]]
|
||||
if md:
|
||||
diffs += len(md)
|
||||
print("memory differs at %d words, first at qw 0x%X" % (len(md), md[0] // 16))
|
||||
for k in md[:10]:
|
||||
print(" qw %03X.%s ref %08X C %08X" % (k // 16, XYZW[(k // 4) & 3], struct.unpack_from("<I", m.mem, k)[0], struct.unpack_from("<I", af["mem"], k)[0]))
|
||||
print("kicks:", ["%03X" % k for k in m.kicks][:20], "cycles", m.cyc)
|
||||
print("DIFFS", diffs)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,153 @@
|
||||
# Level programs
|
||||
|
||||
Each RAC1 level carries its own build of the game program. When a level
|
||||
loads, ParseBin (`func_0012DA38`) copies it over the executable's `main`
|
||||
segment (`.lit`, `.bss`, `.data`, the three level vtables and `.text`,
|
||||
0x0015F000 up) and returns its entry point; main calls it (OpenRAC
|
||||
docs/OVERLAYS.md, docs/ASSETS.md "Code overlays"). The wrapper compiles
|
||||
level programs natively, like the executable: C from OpenRAC's
|
||||
`src/overlays` where it exists, everything else translated ahead of time
|
||||
from the level's own bytes by `tools/recomp.py`. Nothing is interpreted at
|
||||
run time. `build.py --levels` picks the levels (default `00`, Veldin 1,
|
||||
where the main menu is).
|
||||
|
||||
## Where the program comes from
|
||||
|
||||
- **At run time** the game itself reads the level's data section from the
|
||||
disc into the buffer `D_0015EF4C` points at (exe code, before ParseBin).
|
||||
The host's ParseBin (`host/src/rac1/levels.c`) walks the records exactly as
|
||||
`src/core/boot.c` does and copies each to its address, so `lit`, `bss`,
|
||||
`data` and the vtables land where the PS2 puts them. It then recognises
|
||||
the program (entry point and text record against `gen/level_table.c`),
|
||||
fills the executable's old text and the level's text with int3, installs
|
||||
the level's trampolines and makes the level's table the one `rc_dispatch`
|
||||
uses. A program that is not built in stops the run with a log line.
|
||||
- **At build time** recomp.py needs the level's code bytes.
|
||||
`tools/level_dump.py` extracts the seven records and a `manifest.json`
|
||||
(OpenRAC's dump layout) to `build/levels/level_NN/` from the disc image
|
||||
(`--iso`, `RAC1_ISO`, or `iso=` in `build/<platform>/rac1_wrapper.cfg`):
|
||||
table of contents at LBA 1500, the level's header, its data range, whose
|
||||
first word is the overlay's offset. An OpenRAC dump in the decomp repo
|
||||
(`baserom/overlays/level_NN/`) is used instead when present. These are
|
||||
game data; they stay in `build/`.
|
||||
|
||||
## Which code is which
|
||||
|
||||
`config/overlays/functions.tsv` lists every function of every level with
|
||||
its places (`NN:ADDR`). For level NN the build uses those with a place in
|
||||
NN, at that place:
|
||||
|
||||
| Function | Built from |
|
||||
|---|---|
|
||||
| shared / level (`func_LNN_X`) with C in `src/overlays/shared` or `src/overlays/lNN_*` | that C (gen_host.py `--level`) |
|
||||
| the same, `INCLUDE_ASM`, or C with inline asm | the level's bytes (recomp.py `--level`) |
|
||||
| shared, canonical level another one (`func_L00_X` in level 01) | the level's bytes (its C names level 00's data) |
|
||||
| exe kind (`func_X`, a copy of an executable game function) | the level's bytes: the copy's relocations are the level's own (data at the level's addresses, calls to the level's functions), which the executable's C cannot know |
|
||||
| exe kind with a host version (`host/overrides/func_X.c`: memcpy-likes, WAD unpack, movie player, sound init) | that host C, compiled again for the level |
|
||||
| exe kind the host provides in `host/src` (`func_001F9988`) | the same host function |
|
||||
| a table entry inside a function (InitCamera_7 at 0x2EDB68 in level 00), a `jal` target no catalogued function starts at | the level's bytes, from that address (`func_lNN_entry_ADDR`) |
|
||||
| a host function the catalogue does not place (level 00's busy wait at 0x1FEFC8, catalogued as nops plus `func_L00_001FEFE0`) | found by the executable function's bytes; the host function |
|
||||
| a function at several places | each place translated on its own (`func_X__lNNatADDR`); C is shared |
|
||||
|
||||
The level's C is only right for the level it was written for: a shared
|
||||
function's C names its data and callees by its canonical level
|
||||
(`D_L00_...`). Level 00 is the canonical level of everything it has, so all
|
||||
its C applies. In another level, functions whose canonical level is not
|
||||
that level come from its bytes (until C with relocations mapped from the
|
||||
bytes, see Next).
|
||||
|
||||
Assembly-only functions have no definition to take a signature from, so
|
||||
the generator makes one of their declarations the reference (the longest
|
||||
plain `extern` prototype), puts every other prototype in its float/int
|
||||
order and gives recomp.py's C entry the same signature: the EE passes
|
||||
floats and integers in separate registers, so `f(float, void *)` in one
|
||||
file and `f(void *, float)` in another are the same call there, not on
|
||||
x86.
|
||||
|
||||
## Names: one header per level
|
||||
|
||||
The executable and every level link into one program, and their names
|
||||
overlap (the same `func_X` is the executable's function and a different
|
||||
copy in each level; a shared `func_L00_X` exists in 19 levels). So the
|
||||
level pass gives every name it defines or places the suffix `_lNN`:
|
||||
`gen/lNN/lNN_names.h` (`#define func_L00_X func_L00_X_l00`, the same for
|
||||
each main-segment `func_X`, each level data name `D_L00_X`, each alias) is
|
||||
forced into every C file of the level (`-include`), and asm labels
|
||||
(`__asm__("D_L00_X")`, `SDATA(...)`) are rewritten to match. Names the
|
||||
executable pass provides with the same value (the core segment, core data
|
||||
below 0x15F000) are left alone and resolve to the executable's. The
|
||||
generator's own passes work on the original names; only the compiler sees
|
||||
the suffixes. Aliases from the generator's passes (`__h1`, `__imp`) carry
|
||||
the level too (`__l00h1`).
|
||||
|
||||
Level outputs in `build/<platform>/gen/lNN/`:
|
||||
|
||||
| File | What |
|
||||
|---|---|
|
||||
| `overlays_*.c` | the level's C (other levels' functions left out) |
|
||||
| `asm_stubs.c`, `missing.c` | stubs: the first are replaced by recomp.py; the second log calls to names the level's C uses that this level's program does not have |
|
||||
| `rc_funcs_*.c`, `rc_wrappers.c`, `rc_thunks.c`, `rc_table.c` | translated functions (`rcNN_` prefix), their C entries, thunks into C, the dispatch table `rcNN_table` (with the core's translated functions and thunks) |
|
||||
| `tramp_table.c` | `uya_tramp_table_lNN`: every function of the level at every place |
|
||||
| `abs_syms.s` | the level's data and other by-address names (with the suffix) |
|
||||
| `sigs.json`, `asm_entries.json`, `overrides.txt`, `funcs.tsv` | for recomp.py and build.py |
|
||||
|
||||
## Fixes made along the way (both passes)
|
||||
|
||||
- **Function values**: a function named as a value (callback argument,
|
||||
pointer stored in a moby, `m->f74 = func_L00_0028F458`) is its PS2
|
||||
address on the PS2. In level C it now is too (`((__typeof__(f) *)0xADDR)`),
|
||||
so translated code and tables get an address `rc_dispatch` knows, and C
|
||||
calls through it still work (trampoline). Level pass only.
|
||||
- **Labeled prototypes** (`extern void f_x(...) __asm__("f")`) now get the
|
||||
float/int order fix as well (only the order: they are deliberate views).
|
||||
- **Thunks** pass the extra `uya_aN` arguments of extended definitions.
|
||||
- **Quadword accesses** (`lq`/`sq`/`lqc2`/`sqc2`) of translated code keep the
|
||||
EE's masking of the low 4 bits, except on the x86 stack, where C locals
|
||||
are not 16-byte aligned (libvu0's matrix functions on C locals read the
|
||||
wrong data before). A side effect: the executable's attract loop (a run
|
||||
without `UYA_LEVEL`) now gets to its moby drawing (`func_001EE9F8`, a
|
||||
hand-written loop over scratchpad data that toSPR DMA brings in) and
|
||||
stays there after about 16 frames (not investigated yet); with
|
||||
`UYA_LEVEL=0@1` the level is asked for before.
|
||||
- **recomp.py**: a function whose last instruction has a delay slot reads it
|
||||
from the next word; in level mode a function that runs off its end
|
||||
continues into the next one; libgcc (0x11DF10-0x1207B8) is translated
|
||||
(level code calls helpers the executable's C does not).
|
||||
|
||||
## Testing
|
||||
|
||||
`UYA_LEVEL="0@1"` asks for level 0 at vsync 1, as the title menu's "go to
|
||||
level" (`func_0022F4A0`) does, so a run reaches ParseBin without the menu.
|
||||
`UYA_VSYNCS=N` ends a run at vsync N with the list of missing functions.
|
||||
|
||||
```
|
||||
RAC1_ISO=disc.iso UYA_SKIPMOVIES=20 UYA_LEVEL=0@1 UYA_VSYNCS=2500 ./rac1_wrapper SCES_509.16
|
||||
```
|
||||
|
||||
State on 2026-10-10: ParseBin loads level 00 at vsync 769 (after the
|
||||
level's three opening movies), the entry point (`func_L00_002465F8`,
|
||||
translated) runs the level start-up (loaders, cameras, mobys: C and
|
||||
translated code mixed) and enters the level's frame loop; up to vsync 2500
|
||||
no level function traps. The first frame's moby updates then wait: the update
|
||||
of moby class 834 (`func_L00_002DB278`) starts the scene's dialog stream
|
||||
(`func_L00_00299B68`) and calls `sound_update` and vsync until the stream
|
||||
state (the halfword at 0x0015182A) reaches 3. Since 2026-10-11 the host's 989snd
|
||||
(host/src/rac1/snd989.c) plays the stream and answers "started" then "buffered", so the
|
||||
state goes 1, 2, 3, the scene continues it (4) and the dialog plays to its end. Nothing is drawn (as in the executable: the
|
||||
display set-up and VU1 are not there yet).
|
||||
|
||||
## Next
|
||||
|
||||
- **Exe-kind functions from C.** About 940 of level 00's functions are
|
||||
copies of executable functions that have C in `src/game`; today they are
|
||||
translated from bytes. Comparing each copy with the executable's bytes
|
||||
instruction by instruction gives its relocation map (each `%hi/%lo` and
|
||||
`$gp` reference, each `jal`): the executable's C can then be compiled for
|
||||
the level with those names mapped (a per-function rename, as the header
|
||||
does per level), and checked: a copy whose map is not consistent stays
|
||||
translated. The same map makes shared C usable in levels other than its
|
||||
canonical one.
|
||||
- **Other levels**: `--levels 00,01,...` builds them (shared functions of
|
||||
another canonical level from bytes); only level 00 has been run.
|
||||
- The level's assembly-only functions shrink as OpenRAC matches them; the
|
||||
build picks the C up by itself.
|
||||
@@ -0,0 +1,43 @@
|
||||
/* rac1_common.h: replaces OpenRAC's include/common.h when the game's C is
|
||||
* built for a PC (gen_host.py copies it to gen/include/common.h).
|
||||
*
|
||||
* The host is a 32-bit x86 program with PS2 RAM mapped at its real addresses,
|
||||
* so pointers are 4 bytes and structs keep their PS2 layout. `long` is
|
||||
* rewritten to `long long` by gen_host.py (the EE's long is 64-bit); build
|
||||
* with -malign-double so 64-bit fields keep 8-byte alignment. */
|
||||
#ifndef COMMON_H
|
||||
#define COMMON_H
|
||||
|
||||
#include "uya_host.h"
|
||||
#include "names.h"
|
||||
|
||||
/* Matching-only placement attributes: no meaning on the host. */
|
||||
#define NOT_SDA
|
||||
#define MACRO_ADDR
|
||||
/* SDATA(D_x) names a scalar by another C name: an asm label to the address symbol
|
||||
* (expanded first: a level program's names carry its suffix, gen/lNN/lNN_names.h) */
|
||||
#define UYA_SYMSTR_(x) #x
|
||||
#define UYA_SYMSTR(x) UYA_SYMSTR_(x)
|
||||
#ifdef _WIN32
|
||||
#define SDATA(sym) __asm__("_" UYA_SYMSTR(sym))
|
||||
#else
|
||||
#define SDATA(sym) __asm__(UYA_SYMSTR(sym))
|
||||
#endif
|
||||
|
||||
/* The decomp's quadword copy and clear (lq/sq inline asm on the EE). */
|
||||
static __inline__ void qcopy(void *dst, void *src) { __builtin_memcpy(dst, src, 16); }
|
||||
static __inline__ void qcopy_nc(void *dst, void *src) { __builtin_memcpy(dst, src, 16); }
|
||||
static __inline__ void qzero(void *p) { __builtin_memset(p, 0, 16); }
|
||||
|
||||
/* EE FPU division for C patched with it (tools/source_patches.py): a division
|
||||
* by zero gives +-MAX (the sign of a XOR b), never inf or NaN. */
|
||||
static __inline__ float ee_fdiv(float a, float b)
|
||||
{
|
||||
if (b == 0.0f) {
|
||||
unsigned ua, ub; __builtin_memcpy(&ua, &a, 4); __builtin_memcpy(&ub, &b, 4);
|
||||
unsigned r = ((ua ^ ub) & 0x80000000u) | 0x7F7FFFFFu; float f; __builtin_memcpy(&f, &r, 4); return f;
|
||||
}
|
||||
return a / b;
|
||||
}
|
||||
|
||||
#endif /* COMMON_H */
|
||||
@@ -0,0 +1,33 @@
|
||||
/* rac1_levels.h: the level programs compiled into the wrapper (docs/LEVELS.md).
|
||||
*
|
||||
* Each level carries its own build of the game program; ParseBin copies it
|
||||
* over the executable's main segment. build.py compiles the levels it is
|
||||
* asked for (--levels) and lists them in gen/level_table.c; ParseBin
|
||||
* (host/src/rac1/levels.c) finds the one being loaded there and switches the
|
||||
* trampolines and the recompiled code's dispatch table to it. */
|
||||
#ifndef RAC1_LEVELS_H
|
||||
#define RAC1_LEVELS_H
|
||||
#include "uya_runtime.h"
|
||||
|
||||
typedef struct RcCtx RcCtx;
|
||||
typedef struct { uint32_t addr; void (*fn)(RcCtx *); } rac1_rc_row_t;
|
||||
|
||||
typedef struct {
|
||||
int id; /* level index (docs/OVERLAYS.md, Levels) */
|
||||
uint32_t entry; /* the entry point ParseBin returns */
|
||||
uint32_t text_addr, text_bytes; /* its text record: how a loaded program is recognised */
|
||||
const uya_tramp_t *tramps; /* uya_tramp_table_lNN */
|
||||
const int *tramp_count;
|
||||
const rac1_rc_row_t *rc_table; /* rcNN_table: its recompiled functions and thunks by address */
|
||||
const int *rc_count;
|
||||
} rac1_level_t;
|
||||
|
||||
extern const rac1_level_t rac1_levels[];
|
||||
extern const int rac1_level_count;
|
||||
|
||||
/* host/src/rac1/rc_runtime.c: the table rc_dispatch looks addresses up in */
|
||||
void rc_set_table(const rac1_rc_row_t *table, int count);
|
||||
/* host/src/runtime.c */
|
||||
void uya_install_tramp_table(const uya_tramp_t *table, int count);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,162 @@
|
||||
/* rc.h: runtime for code produced by tools/recomp.py.
|
||||
*
|
||||
* recomp.py translates the game's assembly-only functions (MIPS R5900, with
|
||||
* MMI and VU0 macro instructions) into C ahead of time. Each becomes a C
|
||||
* function over an RcCtx (the EE's registers); it is compiled with the rest of
|
||||
* the program, so nothing is translated or interpreted while the game runs.
|
||||
* Memory is the PS2's own address space, mapped at the same addresses. */
|
||||
#ifndef RC_H
|
||||
#define RC_H
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
/* the helpers below are tiny: always inline them (the generated code is built
|
||||
* with -fno-builtin -fno-inline-functions, which otherwise leaves each one,
|
||||
* and the memcpy in it, a call) */
|
||||
#define RC_INLINE static inline __attribute__((always_inline))
|
||||
|
||||
typedef int8_t s8; typedef uint8_t u8; typedef int16_t s16; typedef uint16_t u16;
|
||||
typedef int32_t s32; typedef uint32_t u32; typedef int64_t s64; typedef uint64_t u64;
|
||||
typedef float f32;
|
||||
|
||||
typedef union {
|
||||
u64 d[2]; s64 sd[2]; u32 w[4]; s32 sw[4]; u16 h[8]; s16 sh[8]; u8 b[16]; s8 sb[16];
|
||||
} R128;
|
||||
|
||||
typedef struct RcCtx {
|
||||
R128 r[32];
|
||||
R128 hi, lo; /* hi/lo in the low doubleword, hi1/lo1 in the high one */
|
||||
u32 sa;
|
||||
f32 f[32];
|
||||
f32 facc; /* the EE FPU accumulator */
|
||||
int fcc; /* FPU condition */
|
||||
} RcCtx;
|
||||
|
||||
typedef struct {
|
||||
f32 vf[32][4];
|
||||
f32 acc[4];
|
||||
f32 q, i, p, r;
|
||||
u32 vi[16];
|
||||
u32 clip, status, mac;
|
||||
/* micro mode (tools/vu_recomp.py, host/include/vu.h): CMSAR0, where the
|
||||
* last microprogram ended (MSCNT), and the results still in the pipeline:
|
||||
* MAC/status and clip flags become visible 4 cycles after the op issues,
|
||||
* Q 7 (13 for RSQRT) cycles after the DIV. */
|
||||
u32 cmsar0, tpc;
|
||||
int susp; /* VU0: parked in a wait loop for the EE (tpc = where to resume) */
|
||||
u32 st_int, cl_int;
|
||||
u32 fp_n, fp_h, fp_rdy[4], fp_mac[4], fp_st[4];
|
||||
u32 cp_n, cp_h, cp_rdy[4], cp_val[4];
|
||||
u32 q_pend, q_rdy; f32 q_new;
|
||||
} RcVu0;
|
||||
extern RcVu0 rc_vu0;
|
||||
|
||||
/* the shared stack pointer for nested C <-> recompiled calls */
|
||||
extern u32 rc_sp;
|
||||
#define RC_GP 0x00166D00u
|
||||
|
||||
void rc_dispatch(RcCtx *c, u32 addr); /* call any function by PS2 address */
|
||||
void rc_vcallms(RcCtx *c, u32 addr);
|
||||
void rc_vu0_resume(void); /* a VU0 microprogram (host/src/gfx/vu.c) */
|
||||
void vu_run(int vu, u32 pc); /* run the translated microprogram at pc */
|
||||
void rc_syscall(RcCtx *c);
|
||||
void rc_break(u32 pc);
|
||||
void rc_bad(u32 pc, const char *what);
|
||||
void rc_enter(RcCtx *c);
|
||||
u32 rc_cop0(int r);
|
||||
|
||||
/* ---- memory: KSEG aliases of RAM (0x20000000-0x3FFFFFFF uncached/accelerated) fold back ---- */
|
||||
RC_INLINE uintptr_t RC_A(u32 a) { return (uintptr_t)((((a >> 28) == 2 || (a >> 28) == 3) && (a & 0x0FFFFFFFu) < 0x02000000u) ? (a & 0x0FFFFFFFu) : a); }
|
||||
#define M8(a) (*(u8 *)RC_A(a))
|
||||
#define M16(a) (*(u16 *)RC_A(a))
|
||||
#define M32(a) (*(u32 *)RC_A(a))
|
||||
#define M64(a) (*(u64 *)RC_A(a))
|
||||
#define MF(a) (*(f32 *)RC_A(a))
|
||||
/* The EE's hardware registers (0x10000000-0x1000FFFF): a store that starts a
|
||||
* DMA channel runs the transfer at once; FIFO writes go to the VIF/GIF. */
|
||||
void rc_io_write32(u32 a, u32 v);
|
||||
void rc_io_write128(u32 a, const void *v);
|
||||
RC_INLINE int rc_is_io(u32 a) { return (a & 0xFFFF0000u) == 0x10000000u; }
|
||||
RC_INLINE void rc_sw(u32 a, u32 v) { if (rc_is_io(a)) rc_io_write32(a, v); else M32(a) = v; }
|
||||
RC_INLINE void rc_sd(u32 a, u64 v) { if (rc_is_io(a)) { rc_io_write32(a, (u32)v); rc_io_write32(a + 4, (u32)(v >> 32)); } else M64(a) = v; }
|
||||
/* Quadword accesses (lq, sq, lqc2, sqc2) ignore the low 4 address bits on the
|
||||
* EE, and the game relies on it. The one exception on the host: a local array
|
||||
* of the C code on the x86 stack, which is not 16-byte aligned as the EE's
|
||||
* would be, is used where it is (rc_cstack_lo/hi: the x86 stack, set by
|
||||
* rc_enter). */
|
||||
extern u32 rc_cstack_lo, rc_cstack_hi;
|
||||
RC_INLINE uintptr_t RC_QA(u32 a)
|
||||
{
|
||||
return RC_A(a - rc_cstack_lo < rc_cstack_hi - rc_cstack_lo ? a : (a & ~15u));
|
||||
}
|
||||
RC_INLINE void rc_lq(R128 *d, u32 a) { __builtin_memcpy(d, (void *)RC_QA(a), 16); }
|
||||
RC_INLINE void rc_sq(const R128 *s, u32 a) { if (rc_is_io(a)) rc_io_write128(a & ~15u, s); else __builtin_memcpy((void *)RC_QA(a), s, 16); }
|
||||
|
||||
#define SE32(x) ((u64)(s64)(s32)(u32)(x))
|
||||
|
||||
/* unaligned loads/stores (little-endian EE) */
|
||||
RC_INLINE u64 rc_lwl(u64 rt, u32 a) { u32 k = (a & 3) * 8; u32 w = M32(a & ~3u);
|
||||
u32 keep = k == 24 ? 0 : (0x00FFFFFFu >> k); return SE32(((u32)rt & keep) | (w << (24 - k))); }
|
||||
RC_INLINE u64 rc_lwr(u64 rt, u32 a) { u32 k = (a & 3) * 8; u32 w = M32(a & ~3u);
|
||||
if (k == 0) return SE32(w);
|
||||
u32 keep = ~(0xFFFFFFFFu >> k); return (rt & 0xFFFFFFFF00000000ull) | (((u32)rt & keep) | (w >> k)); }
|
||||
RC_INLINE void rc_swl(u64 rt, u32 a) { u32 k = (a & 3) * 8; u32 *p = (u32 *)RC_A(a & ~3u);
|
||||
u32 keep = k == 24 ? 0 : (0xFFFFFF00u << k); *p = (*p & keep) | ((u32)rt >> (24 - k)); }
|
||||
RC_INLINE void rc_swr(u64 rt, u32 a) { u32 k = (a & 3) * 8; u32 *p = (u32 *)RC_A(a & ~3u);
|
||||
u32 keep = k == 0 ? 0 : (0x00FFFFFFu >> (24 - k)); *p = (*p & keep) | ((u32)rt << k); }
|
||||
RC_INLINE u64 rc_ldl(u64 rt, u32 a) { u32 k = (a & 7) * 8; u64 w = M64(a & ~7u);
|
||||
u64 keep = k == 56 ? 0 : (0x00FFFFFFFFFFFFFFull >> k); return (rt & keep) | (w << (56 - k)); }
|
||||
RC_INLINE u64 rc_ldr(u64 rt, u32 a) { u32 k = (a & 7) * 8; u64 w = M64(a & ~7u);
|
||||
u64 keep = k == 0 ? 0 : ~(0xFFFFFFFFFFFFFFFFull >> k); return (rt & keep) | (w >> k); }
|
||||
RC_INLINE void rc_sdl(u64 rt, u32 a) { u32 k = (a & 7) * 8; u64 *p = (u64 *)RC_A(a & ~7u);
|
||||
u64 keep = k == 56 ? 0 : (0xFFFFFFFFFFFFFF00ull << k); *p = (*p & keep) | (rt >> (56 - k)); }
|
||||
RC_INLINE void rc_sdr(u64 rt, u32 a) { u32 k = (a & 7) * 8; u64 *p = (u64 *)RC_A(a & ~7u);
|
||||
u64 keep = k == 0 ? 0 : (0x00FFFFFFFFFFFFFFull >> (56 - k)); *p = (*p & keep) | (rt << k); }
|
||||
|
||||
/* ---- integer helpers ---- */
|
||||
RC_INLINE s32 rc_plzcw1(u32 x) { u32 s = x >> 31; s32 n = 0; for (int b = 31; b >= 0 && ((x >> b) & 1) == s; b--) n++; return n - 1; }
|
||||
RC_INLINE s32 rc_clamp16(s32 v) { return v > 32767 ? 32767 : v < -32768 ? -32768 : v; }
|
||||
|
||||
/* ---- floats ---- */
|
||||
RC_INLINE u32 rc_fbits(f32 f) { u32 u; __builtin_memcpy(&u, &f, 4); return u; }
|
||||
RC_INLINE f32 rc_bitsf(u32 u) { f32 f; __builtin_memcpy(&f, &u, 4); return f; }
|
||||
RC_INLINE s32 rc_cvtws(f32 f) { /* EE: truncates, saturates */
|
||||
if (f >= 2147483647.0f) return 0x7FFFFFFF;
|
||||
if (f <= -2147483648.0f) return (s32)0x80000000u;
|
||||
return (s32)f;
|
||||
}
|
||||
RC_INLINE f32 rc_fsqrt(f32 x) { return __builtin_sqrtf(__builtin_fabsf(x)); }
|
||||
/* The EE FPU and the VUs have no infinities: a result that overflows is
|
||||
* +-MAX (0x7F7FFFFF). Without this, MAX * MAX (FastArcSin of a huge value
|
||||
* after a division by zero) becomes inf and then NaN, which a camera's
|
||||
* matrices keep for good. */
|
||||
RC_INLINE f32 rc_fc(f32 x) { u32 b = rc_fbits(x); return ((b & 0x7F800000u) == 0x7F800000u) ? rc_bitsf((b & 0x80000000u) | 0x7F7FFFFFu) : x; }
|
||||
RC_INLINE f32 rc_fdiv(f32 a, f32 b) { if (b == 0.0f) return (rc_fbits(a) ^ rc_fbits(b)) & 0x80000000u ? -3.4028235e38f : 3.4028235e38f; return a / b; }
|
||||
RC_INLINE s32 rc_ftoi(f32 f, int shift) { return rc_cvtws(f * (f32)(1 << shift)); }
|
||||
RC_INLINE f32 rc_itof(s32 i, int shift) { return (f32)i / (f32)(1 << shift); }
|
||||
|
||||
/* COP2 macro mode: an FMAC op (ADD/SUB/MUL/MADD/MSUB/OPMULA/OPMSUB and their
|
||||
* ACC forms) sets the MAC flags (cfc2 $vi17) from its result, per written
|
||||
* component (Z bits 3..0 = x..w, S bits 7..4), the others cleared, and the
|
||||
* status flag's Z/S/U/O bits (cfc2 $vi16) from them, with the sticky copies
|
||||
* (bits 6..9). The collision code (coll_sphere, func_L00_001F10E0) tests the
|
||||
* signs of a VSUB this way: `cfc2 $at, $vi17; andi $at, 0xE0`. */
|
||||
RC_INLINE void rc_vu0_flags(const f32 *v, u32 dest)
|
||||
{
|
||||
u32 mac = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (!(dest & (8u >> i))) continue;
|
||||
u32 b; __builtin_memcpy(&b, &v[i], 4);
|
||||
if ((b & 0x7FFFFFFFu) == 0) mac |= 8u >> i;
|
||||
if (b >> 31) mac |= 0x80u >> i;
|
||||
if ((b & 0x7F800000u) == 0x7F800000u) mac |= 0x8000u >> i; /* overflow */
|
||||
}
|
||||
rc_vu0.mac = mac;
|
||||
u32 st = (mac & 0xF ? 1u : 0) | (mac & 0xF0 ? 2u : 0) | (mac & 0xF00 ? 4u : 0) | (mac & 0xF000 ? 8u : 0);
|
||||
rc_vu0.status = (rc_vu0.status & ~0xFu) | st | (st << 6);
|
||||
}
|
||||
|
||||
#define VF(n) rc_vu0.vf[n]
|
||||
#define VACC rc_vu0.acc
|
||||
#endif
|
||||
@@ -0,0 +1,46 @@
|
||||
/* uya_host.h: replaces the decomp's common.h when frontbin's C is built for a PC.
|
||||
*
|
||||
* Memory model: the host is a 32-bit x86 program, so pointers are 4 bytes like
|
||||
* on the EE and every struct keeps its PS2 layout. PS2 main RAM is mapped at its
|
||||
* real addresses (0x00100000-0x01FFFFFF), so the decomp's globals (declared
|
||||
* extern, placed by address through abs_syms.s) and its absolute constants
|
||||
* point at real memory. long is rewritten to long long by gen_host.py (the
|
||||
* EE's long is 64-bit); build with -malign-double so 64-bit fields keep 8-byte
|
||||
* alignment.
|
||||
*/
|
||||
#ifndef UYA_HOST_H
|
||||
#define UYA_HOST_H
|
||||
|
||||
typedef signed char s8;
|
||||
typedef unsigned char u8;
|
||||
typedef signed short s16;
|
||||
typedef unsigned short u16;
|
||||
typedef signed int s32;
|
||||
typedef unsigned int u32;
|
||||
typedef signed long long s64;
|
||||
typedef unsigned long long u64;
|
||||
typedef float f32;
|
||||
typedef double f64;
|
||||
|
||||
/* The decomp's assembly-inclusion macros have no meaning on the host;
|
||||
* gen_host.py removes their uses, these are a fallback. */
|
||||
#define INCLUDE_ASM(FOLDER, NAME)
|
||||
#define INCLUDE_RODATA(FOLDER, NAME)
|
||||
#define ASM_FUNC(FOLDER, NAME)
|
||||
#define LINKER_REMNANT(FOLDER, NAME)
|
||||
#define TEXT_PADDING(N)
|
||||
|
||||
/* The EE's MMI plzcw on one word: leading bits equal to the sign bit, minus one. */
|
||||
static inline s32 uya_plzcw(u32 x)
|
||||
{
|
||||
u32 s = x >> 31;
|
||||
s32 n = 0;
|
||||
for (int b = 31; b >= 0 && ((x >> b) & 1) == s; b--) n++;
|
||||
return n - 1;
|
||||
}
|
||||
|
||||
/* A function the host does not implement yet: logs once per name. */
|
||||
void uya_unimpl(const char *name);
|
||||
void uya_log(const char *fmt, ...);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,61 @@
|
||||
/* uya_runtime.h: the host runtime's own interface (not seen by frontbin code). */
|
||||
#ifndef UYA_RUNTIME_H
|
||||
#define UYA_RUNTIME_H
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* PS2 address ranges the host maps at their real addresses. */
|
||||
#define PS2_RAM_LO 0x00100000u /* the game never touches the EE kernel area below 1 MB */
|
||||
#define PS2_RAM_HI 0x02000000u /* 32 MB of EE main RAM */
|
||||
#define PS2_SPR 0x70000000u /* scratchpad */
|
||||
#define PS2_SPR_SIZE 0x4000u
|
||||
#define PS2_EE_REGS 0x10000000u /* timers, IPU, GIF, VIF0/1, DMA, INTC... */
|
||||
#define PS2_EE_REGS_SZ 0x00010000u
|
||||
#define PS2_VU_MEM 0x11000000u /* VU0/VU1 code and data memory */
|
||||
#define PS2_VU_MEM_SZ 0x00010000u
|
||||
#define PS2_GS_PRIV 0x12000000u /* GS privileged registers */
|
||||
#define PS2_GS_PRIV_SZ 0x00002000u
|
||||
|
||||
typedef struct { uint32_t ps2_addr; void *host_fn; } uya_tramp_t;
|
||||
extern const uya_tramp_t uya_tramp_table[];
|
||||
extern const int uya_tramp_count;
|
||||
|
||||
/* platform layer */
|
||||
int plat_map_fixed(uint32_t addr, uint32_t size, int exec); /* 0 on success */
|
||||
int plat_install_trap_handler(void); /* calls into unmapped PS2 code */
|
||||
void plat_flush_icache(void *p, size_t n);
|
||||
const char *plat_exe_dir(void); /* folder of the running program */
|
||||
void plat_at_exit(void);
|
||||
uint64_t plat_time_us(void); /* monotonic clock */
|
||||
void plat_sleep_us(uint64_t us);
|
||||
/* video output: show a w x h 32-bit image (bytes B, G, R, X); returns 0 once the window was closed */
|
||||
int plat_present(const uint32_t *rgbx, int w, int h);
|
||||
void plat_movie_started(void); /* testing hook (Linux UYA_SKIPMOVIES) */
|
||||
int plat_key_skip(void); /* a skip key (Enter/Space/Esc) was pressed since the last call */
|
||||
void plat_poll(void); /* keep the window responsive; exits when it was closed */
|
||||
/* pad buttons held on the keyboard, PS2 bit order, 1 = pressed (see host/src/sdk/pad.c) */
|
||||
unsigned plat_pad_buttons(void);
|
||||
/* sound: plays `frames` interleaved stereo 16-bit samples once; plat_audio_stop ends it early.
|
||||
* `tag` names the dump file on Linux (UYA_DUMP). */
|
||||
void plat_audio_play(const int16_t *lr, size_t frames, int rate, const char *tag);
|
||||
void plat_audio_stop(void);
|
||||
/* continuous 48 kHz stereo output for the sound effects mixer (host/src/sdk/sound.c) */
|
||||
void plat_stream_write(const int16_t *lr, int frames);
|
||||
int plat_stream_queued(void); /* frames written but not played yet */
|
||||
int uya_disc_read(uint32_t lsn, uint32_t count, void *buf); /* 0 on success */
|
||||
uint32_t uya_frame_count(void); /* vsyncs so far (host/src/sdk/graph.c) */
|
||||
|
||||
/* runtime */
|
||||
int uya_load_elf_sections(const char *path, const char *const *sections);
|
||||
void uya_install_trampolines(void);
|
||||
void uya_fill_code_traps(uint32_t lo, uint32_t hi);
|
||||
void uya_log(const char *fmt, ...);
|
||||
void uya_log_open(void); /* also log to rac1_log.txt next to the program */
|
||||
void uya_trap_call(uint32_t ps2_addr, uint32_t ret_addr, const uint32_t *args); /* from the trap handler */
|
||||
void uya_report(void);
|
||||
int uya_main(int argc, char **argv);
|
||||
int uya_disc_open(const char *path);
|
||||
void uya_disc_boot(void);
|
||||
void uya_boot(void); /* boot_elf's set-up before the menu (host/src/boot.c) */
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,146 @@
|
||||
/* vu.h: runtime for the VU microprograms translated by tools/vu_recomp.py.
|
||||
*
|
||||
* Each microprogram the game uploads (VU0 through VIF0, VU1 through VIF1) is
|
||||
* translated to C at build time from the user's executable. A translation is a
|
||||
* function over an RcVu0 (VU0 uses rc_vu0, the registers it shares with the
|
||||
* EE's macro mode; VU1 has rc_vu1). Data memory is the real address range:
|
||||
* VU0 at 0x11004000 (4 KB), VU1 at 0x1100C000 (16 KB).
|
||||
*
|
||||
* The VU pipeline is visible to these programs in three places and modelled
|
||||
* here: MAC/status and clip flags written by an op are seen by flag reads 4
|
||||
* cycles later, and Q from DIV/SQRT (RSQRT) 7 (13) cycles later. The
|
||||
* translator counts cycles (with the stalls it can see statically) in a local
|
||||
* `cyc`; results are queued with the cycle they become visible at. */
|
||||
#ifndef VU_H
|
||||
#define VU_H
|
||||
#include "rc.h"
|
||||
|
||||
extern RcVu0 rc_vu1;
|
||||
|
||||
#ifndef VU_TRACE
|
||||
#define VU_TRACE(pc) /* testing hook: each instruction pair's address */
|
||||
#endif
|
||||
|
||||
/* VU data memory, by quadword address */
|
||||
#define VU0_MEM(qa) ((u32 *)(uintptr_t)(0x11004000u + (((u32)(qa) & 0xFFu) << 4)))
|
||||
#define VU1_MEM(qa) ((u32 *)(uintptr_t)(0x1100C000u + (((u32)(qa) & 0x3FFu) << 4)))
|
||||
|
||||
RC_INLINE u32 vu_u(f32 f) { u32 u; __builtin_memcpy(&u, &f, 4); return u; }
|
||||
RC_INLINE f32 vu_f(u32 u) { f32 f; __builtin_memcpy(&f, &u, 4); return f; }
|
||||
|
||||
/* MAC flag bits for one component (x = 0 .. w = 3) of a result */
|
||||
/* an FMAC result as the VU has it: no inf or NaN, +-MAX instead (counted: FPSLOG) */
|
||||
extern u32 vu_nonfinite;
|
||||
RC_INLINE f32 vu_fc(f32 x)
|
||||
{
|
||||
u32 b = vu_u(x);
|
||||
if (__builtin_expect((b & 0x7F800000u) == 0x7F800000u, 0)) { vu_nonfinite++; b = (b & 0x80000000u) | 0x7F7FFFFFu; __builtin_memcpy(&x, &b, 4); }
|
||||
return x;
|
||||
}
|
||||
RC_INLINE u32 vu_macbits(f32 r, int c)
|
||||
{
|
||||
u32 u = vu_u(r), m = 0;
|
||||
if ((u & 0x7FFFFFFFu) == 0) m |= 8u >> c;
|
||||
if (u >> 31) m |= 0x80u >> c;
|
||||
return m;
|
||||
}
|
||||
|
||||
/* ---- MAC/status flag pipeline ---- */
|
||||
RC_INLINE void vu_fl_sync(RcVu0 *v, u32 cyc)
|
||||
{
|
||||
while (v->fp_n && (s32)(cyc - v->fp_rdy[v->fp_h]) >= 0) {
|
||||
v->mac = v->fp_mac[v->fp_h]; v->status = v->fp_st[v->fp_h];
|
||||
v->fp_h = (v->fp_h + 1) & 3; v->fp_n--;
|
||||
}
|
||||
}
|
||||
RC_INLINE void vu_fl_push(RcVu0 *v, u32 cyc, u32 mac)
|
||||
{
|
||||
u32 st = (v->st_int & 0xFC0u) | ((mac & 0xF) ? 1 : 0) | ((mac & 0xF0) ? 2 : 0);
|
||||
st |= (st & 3) << 6;
|
||||
v->st_int = st;
|
||||
if (v->fp_n == 4) { v->mac = v->fp_mac[v->fp_h]; v->status = v->fp_st[v->fp_h]; v->fp_h = (v->fp_h + 1) & 3; v->fp_n--; }
|
||||
u32 k = (v->fp_h + v->fp_n) & 3;
|
||||
v->fp_rdy[k] = cyc + 4; v->fp_mac[k] = mac; v->fp_st[k] = st; v->fp_n++;
|
||||
}
|
||||
RC_INLINE u32 vu_status(RcVu0 *v, u32 cyc) { vu_fl_sync(v, cyc); return v->status; }
|
||||
RC_INLINE u32 vu_mac(RcVu0 *v, u32 cyc) { vu_fl_sync(v, cyc); return v->mac; }
|
||||
RC_INLINE void vu_fsset(RcVu0 *v, u32 cyc, u32 imm)
|
||||
{
|
||||
vu_fl_sync(v, cyc + 8);
|
||||
v->status = (v->status & 0x3F) | (imm & 0xFC0); v->st_int = v->status;
|
||||
}
|
||||
|
||||
/* ---- clip flag pipeline ---- */
|
||||
RC_INLINE void vu_cl_sync(RcVu0 *v, u32 cyc)
|
||||
{
|
||||
while (v->cp_n && (s32)(cyc - v->cp_rdy[v->cp_h]) >= 0) {
|
||||
v->clip = v->cp_val[v->cp_h]; v->cp_h = (v->cp_h + 1) & 3; v->cp_n--;
|
||||
}
|
||||
}
|
||||
RC_INLINE void vu_clipw(RcVu0 *v, u32 cyc, const f32 *fs, f32 w)
|
||||
{
|
||||
f32 a = __builtin_fabsf(w);
|
||||
u32 f = (fs[0] > a) | ((fs[0] < -a) << 1) | ((fs[1] > a) << 2) | ((fs[1] < -a) << 3) | ((fs[2] > a) << 4) | ((fs[2] < -a) << 5);
|
||||
v->cl_int = ((v->cl_int << 6) | f) & 0xFFFFFF;
|
||||
if (v->cp_n == 4) { v->clip = v->cp_val[v->cp_h]; v->cp_h = (v->cp_h + 1) & 3; v->cp_n--; }
|
||||
u32 k = (v->cp_h + v->cp_n) & 3;
|
||||
v->cp_rdy[k] = cyc + 4; v->cp_val[k] = v->cl_int; v->cp_n++;
|
||||
}
|
||||
RC_INLINE u32 vu_clip(RcVu0 *v, u32 cyc) { vu_cl_sync(v, cyc); return v->clip; }
|
||||
RC_INLINE void vu_fcset(RcVu0 *v, u32 cyc, u32 imm)
|
||||
{
|
||||
vu_cl_sync(v, cyc + 8);
|
||||
v->clip = v->cl_int = imm & 0xFFFFFF;
|
||||
}
|
||||
|
||||
/* ---- Q ---- */
|
||||
RC_INLINE f32 vu_q(RcVu0 *v, u32 cyc)
|
||||
{
|
||||
if (v->q_pend && (s32)(cyc - v->q_rdy) >= 0) { v->q = v->q_new; v->q_pend = 0; }
|
||||
return v->q;
|
||||
}
|
||||
/* WAITQ, or a new DIV while one is in flight: stall until Q is written */
|
||||
RC_INLINE u32 vu_qwait(RcVu0 *v, u32 cyc)
|
||||
{
|
||||
if (v->q_pend) { if ((s32)(cyc - v->q_rdy) < 0) cyc = v->q_rdy; v->q = v->q_new; v->q_pend = 0; }
|
||||
return cyc;
|
||||
}
|
||||
RC_INLINE void vu_qset(RcVu0 *v, u32 cyc, int lat, f32 val) { v->q_new = val; v->q_pend = 1; v->q_rdy = cyc + lat; }
|
||||
|
||||
/* end of a microprogram: everything in flight lands */
|
||||
RC_INLINE void vu_settle(RcVu0 *v)
|
||||
{
|
||||
while (v->fp_n) { v->mac = v->fp_mac[v->fp_h]; v->status = v->fp_st[v->fp_h]; v->fp_h = (v->fp_h + 1) & 3; v->fp_n--; }
|
||||
while (v->cp_n) { v->clip = v->cp_val[v->cp_h]; v->cp_h = (v->cp_h + 1) & 3; v->cp_n--; }
|
||||
if (v->q_pend) { v->q = v->q_new; v->q_pend = 0; }
|
||||
v->fp_h = v->cp_h = 0;
|
||||
}
|
||||
|
||||
RC_INLINE f32 vu_div(f32 a, f32 b)
|
||||
{
|
||||
if (b == 0.0f) return ((vu_u(a) ^ vu_u(b)) & 0x80000000u) ? -3.4028235e38f : 3.4028235e38f;
|
||||
return a / b;
|
||||
}
|
||||
RC_INLINE f32 vu_sqrt(f32 a) { return __builtin_sqrtf(__builtin_fabsf(a)); }
|
||||
RC_INLINE f32 vu_rsqrt(f32 a, f32 b)
|
||||
{
|
||||
f32 s = __builtin_sqrtf(__builtin_fabsf(b));
|
||||
if (s == 0.0f) return (vu_u(a) & 0x80000000u) ? -3.4028235e38f : 3.4028235e38f;
|
||||
return a / s;
|
||||
}
|
||||
RC_INLINE s32 vu_ftoi(f32 f, f32 scale)
|
||||
{
|
||||
f32 x = f * scale;
|
||||
if (x >= 2147483647.0f) return 0x7FFFFFFF;
|
||||
if (x <= -2147483648.0f) return (s32)0x80000000u;
|
||||
if (x != x) return 0;
|
||||
return (s32)x;
|
||||
}
|
||||
RC_INLINE u32 vu_itof(u32 i, f32 inv) { return vu_u((f32)(s32)i * inv); }
|
||||
|
||||
/* host side (host/src/gfx/vu.c) */
|
||||
void vu_xgkick(u32 qaddr);
|
||||
u32 vu_vif_top(int vu);
|
||||
u32 vu_vif_itop(int vu);
|
||||
void vu_bad(int vu, u32 pc, const char *what);
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
/* 0x0012DB68 snd_StartSoundSystem (assembly in the decomp): binds the IOP sound driver, which is host code here (host/src/rac1/snd989.c). */
|
||||
#include "common.h"
|
||||
s32 func_0012DB68(void) { uya_log("989snd: sound system started (host IOP driver)"); return 0; }
|
||||
@@ -0,0 +1,4 @@
|
||||
/* 0x0012DDC0 snd_FlushSoundCommands: delivers owed replies, finished bank loads and stream-safe CD reads; nothing stays queued (host/src/rac1/snd989.c) */
|
||||
#include "common.h"
|
||||
s32 rac1_snd_flush(void);
|
||||
s32 func_0012DDC0(void) { return rac1_snd_flush(); }
|
||||
@@ -0,0 +1,3 @@
|
||||
/* 0x0012DFB0 snd_GotReturns: replies are always in */
|
||||
#include "common.h"
|
||||
s32 func_0012DFB0(void) { return 1; }
|
||||
@@ -0,0 +1,11 @@
|
||||
/* 0x0012E060 snd_BankLoadByLoc(lsn, offset): the IOP loads a sound bank from the disc. Returns a bank handle (0 on failure). */
|
||||
#include "common.h"
|
||||
u32 rac1_snd_bank_load_lsn(s32 lsn, s32 off);
|
||||
u32 func_0012E060(s32 lsn, s32 off)
|
||||
{
|
||||
u32 h;
|
||||
*(volatile s32 *)0x0015ED88 = 0;
|
||||
h = rac1_snd_bank_load_lsn(lsn, off);
|
||||
uya_log("989snd: bank at LSN %d (+%d): handle %08X", lsn, off, h);
|
||||
return h;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/* 0x0012E1C8 snd_BankLoadFromEE_CB(addr, callback, arg): command 0x57. The bank is loaded at once; as in the
|
||||
* library, the reply word (the bank handle) lands in D_0015EE00 and snd_FlushSoundCommands calls the callback. */
|
||||
#include "common.h"
|
||||
u32 rac1_snd_bank_load_ee(u32 addr);
|
||||
void func_0012E1C8(s32 addr, void *cb, s64 arg)
|
||||
{
|
||||
*(volatile s32 *)0x0015ED88 = 0;
|
||||
if (*(volatile s32 *)0x0015EDC8 != 0) { uya_log("989snd: bank load from EE while another is pending"); return; }
|
||||
*(volatile s32 *)0x0015EDD0 = (s32)cb;
|
||||
*(volatile s64 *)0x0015EDD8 = arg;
|
||||
*(volatile u32 *)0x0015EE00 = rac1_snd_bank_load_ee((u32)addr);
|
||||
*(volatile s32 *)0x0015EDC8 = 1;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/* 0x0012E688 snd_SendIOPCommandAndWait(cmd, size, data): to the host's sound driver (host/src/rac1/snd989.c) */
|
||||
#include "common.h"
|
||||
s32 rac1_snd_command(s32 cmd, s32 size, const void *data);
|
||||
s32 func_0012E688(s32 cmd, s32 size, void *data) { return rac1_snd_command(cmd, size, data); }
|
||||
@@ -0,0 +1,9 @@
|
||||
/* 0x0012E820 snd_SendIOPCommandNoWait(cmd, size, data, callback, arg): the reply goes to the callback at the next flush */
|
||||
#include "common.h"
|
||||
s32 rac1_snd_command(s32 cmd, s32 size, const void *data);
|
||||
void rac1_snd_owe(void *fn, s64 arg, s32 result);
|
||||
void func_0012E820(s32 cmd, s32 size, void *data, void *cb, s64 arg)
|
||||
{
|
||||
s32 r = rac1_snd_command(cmd, size, data);
|
||||
rac1_snd_owe(cb, arg, r);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
/* 0x0012EB18 snd_SendCurrentBatch (assembly in the decomp): commands are handled when sent */
|
||||
#include "common.h"
|
||||
void func_0012EB18(void) {}
|
||||
@@ -0,0 +1,40 @@
|
||||
/* 0x0012F4A8 wad: reads level arg0's header (0x2434 bytes) from the sector in
|
||||
* the table of contents (D_0013A548, 8 bytes per level) into D_0013A5E0.
|
||||
* Same as the game's (src/core/wad.c), plus a testing switch:
|
||||
* UYA_LEVEL=n (or n@v, see host/src/rac1/boot.c) makes the first level the
|
||||
* game loads (level 0) level n instead, and sets the current level
|
||||
* (D_0015EE84) to n to match. Without UYA_LEVEL nothing changes. */
|
||||
#include "common.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
extern int func_00121750(int, int, void *, void *);
|
||||
extern int func_00120F30(int);
|
||||
extern void func_00122598(int);
|
||||
extern int func_00121930(void);
|
||||
|
||||
int func_0012F4A8(int arg0)
|
||||
{
|
||||
static int used;
|
||||
if (!used) {
|
||||
const char *e = getenv("UYA_LEVEL");
|
||||
used = 1;
|
||||
if (e && arg0 == 0) {
|
||||
arg0 = atoi(e);
|
||||
*(volatile s32 *)0x0015EE84 = arg0;
|
||||
uya_log("UYA_LEVEL: loading level %d instead of level 0 (testing)", arg0);
|
||||
}
|
||||
}
|
||||
char buf[0x2800];
|
||||
char hdr[4];
|
||||
const int *entry = (const int *)(uintptr_t)(0x0013A548 + arg0 * 8);
|
||||
hdr[0] = 0x20;
|
||||
hdr[1] = *(volatile unsigned char *)0x0015EE58;
|
||||
hdr[2] = 0;
|
||||
hdr[3] = 0;
|
||||
do {
|
||||
func_00121750(*entry, 5, buf, hdr);
|
||||
while (func_00120F30(1) != 0) func_00122598(0);
|
||||
} while (func_00121930() != 0);
|
||||
for (unsigned i = 0; i < 0x2434; i++) ((char *)(uintptr_t)0x0013A5E0)[i] = buf[i];
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "common.h"
|
||||
/* 0x001F9908 (fastfunc, hand-written): count down the timer *p.
|
||||
* 0 stays 0 and returns 1; otherwise it goes down by one and the result is 2
|
||||
* when it reaches 0, else 0. */
|
||||
s32 func_001F9908(s32 *p)
|
||||
{
|
||||
s32 x = *p;
|
||||
if (x == 0) return 1;
|
||||
x = (x > 1 ? x : 1) - 1;
|
||||
*p = x;
|
||||
return x > 0 ? 0 : 2;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#include "common.h"
|
||||
/* 0x001F9938 (fastfunc, hand-written): func_001F9908 on a 16-bit timer. */
|
||||
s32 func_001F9938(s16 *p)
|
||||
{
|
||||
s32 x = *p;
|
||||
if (x == 0) return 1;
|
||||
x = (x > 1 ? x : 1) - 1;
|
||||
*p = (s16)x;
|
||||
return x > 0 ? 0 : 2;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#include "common.h"
|
||||
/* 0x001F9968 (fastfunc, hand-written): 30 - plzcw(x), the index of the
|
||||
* highest bit that differs from the sign bit. */
|
||||
s32 func_001F9968(u32 x) { return 30 - uya_plzcw(x); }
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "common.h"
|
||||
/* 0x001F9978 (fastfunc, hand-written): `teq $0,$0`, the game's halt. */
|
||||
void func_001F9978(void) { uya_log("game halt (func_001F9978)"); }
|
||||
@@ -0,0 +1,13 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "common.h"
|
||||
/* 0x001F99B0 (fastfunc, hand-written): FastMemSet, a 32-bit pattern over n bytes (at least one word). */
|
||||
void func_001F99B0(u32 *p, u32 v, s32 n)
|
||||
{
|
||||
{ /* testing: UYA_LOADLOG=1 logs the scrubs of freed load buffers (0xDEADBEEF) */
|
||||
static int on = -1;
|
||||
if (on < 0) on = getenv("UYA_LOADLOG") != 0;
|
||||
if (on && v == 0xDEADBEEFu) { uint32_t uya_frame_count(void); uya_log("v%u fill %08X-%08X with DEADBEEF", uya_frame_count(), (u32)(uintptr_t)p, (u32)(uintptr_t)p + n); }
|
||||
}
|
||||
do { *p++ = v; n -= 4; } while (n > 0);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#include "common.h"
|
||||
/* 0x001F99D8 (fastfunc, hand-written): clear n bytes in quadwords (at least one). */
|
||||
void func_001F99D8(void *p, s32 n)
|
||||
{
|
||||
u8 *q = p;
|
||||
do { __builtin_memset(q, 0, 16); q += 16; n -= 16; } while (n > 0);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#include "common.h"
|
||||
/* 0x001F9A00 (fastfunc, hand-written): copy n bytes, by quadwords, words or
|
||||
* bytes depending on alignment. */
|
||||
void func_001F9A00(void *dst, const void *src, s32 n)
|
||||
{
|
||||
if (n > 0) __builtin_memmove(dst, src, (u32)n);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#include "common.h"
|
||||
/* 0x001F9A98 (fastfunc, hand-written): copy n bytes in quadwords (at least one). */
|
||||
void func_001F9A98(void *dst, const void *src, s32 n)
|
||||
{
|
||||
u8 *d = dst; const u8 *s = src;
|
||||
do { __builtin_memcpy(d, s, 16); d += 16; s += 16; n -= 16; } while (n > 0);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#include "common.h"
|
||||
/* 0x001F9AC0 (fastfunc, hand-written): dst = a | b over n bytes, in quadwords (at least one). */
|
||||
void func_001F9AC0(u32 *dst, const u32 *a, const u32 *b, s32 n)
|
||||
{
|
||||
do {
|
||||
for (int i = 0; i < 4; i++) dst[i] = a[i] | b[i];
|
||||
dst += 4; a += 4; b += 4; n -= 16;
|
||||
} while (n > 0);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "common.h"
|
||||
/* 0x001F9B70 (fastfunc, hand-written): abs(x) */
|
||||
s32 func_001F9B70(s32 x) { return x < 0 ? -x : x; }
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "common.h"
|
||||
/* 0x001F9D78 (fastfunc, VU0): do two spheres (xyz centre, w radius) overlap?
|
||||
* 1 when the squared distance is less than the squared sum of the radii. */
|
||||
s32 func_001F9D78(const f32 *a, const f32 *b)
|
||||
{
|
||||
f32 dx = a[0] - b[0], dy = a[1] - b[1], dz = a[2] - b[2], r = a[3] + b[3];
|
||||
return dx * dx + dy * dy + dz * dz - r * r < 0.0f ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "common.h"
|
||||
/* 0x001F9E58 (fastfunc, VU0): clamp the length of src.xyz to max into dst.
|
||||
* Returns 1 when it was scaled, 0 when it was already short enough (or zero);
|
||||
* dst is only written when scaled. */
|
||||
s32 func_001F9E58(f32 *dst, const f32 *src, f32 max)
|
||||
{
|
||||
f32 l2 = src[0] * src[0] + src[1] * src[1] + src[2] * src[2];
|
||||
f32 len = __builtin_sqrtf(l2);
|
||||
if (len == 0.0f) return 0;
|
||||
if (max - len > 0.0f) return 0;
|
||||
f32 q = max / len;
|
||||
dst[0] = src[0] * q; dst[1] = src[1] * q; dst[2] = src[2] * q; dst[3] = src[3];
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
/* func_0020C468(src, dst): unpack a "WAD" compressed block (Insomniac's
|
||||
* LZ77 variant, close to LZO1X) and return the unpacked size.
|
||||
*
|
||||
* Ported from the UYA Wrapper (UYA's copy is func_0039B760). RAC1's retail
|
||||
* version is the same hand-written asm: it checks the "WAD" magic and streams
|
||||
* the input through the scratchpad in 0x2000-byte halves by DMA. Here the
|
||||
* input is read straight from memory; a far match with distance 0 and length
|
||||
* above 1 is the encoder's "go to the next 0x2000-byte chunk" marker, which in
|
||||
* a linear read means skipping to the next chunk boundary of the data (which
|
||||
* starts 0x10 bytes into the block). */
|
||||
#include "common.h"
|
||||
|
||||
s32 func_0020C468(u8 *src, u8 *dst)
|
||||
{
|
||||
u8 *start = dst;
|
||||
{ /* testing: UYA_LOADLOG=1 */
|
||||
static int on = -1;
|
||||
if (on < 0) on = getenv("UYA_LOADLOG") != 0;
|
||||
if (on) { uint32_t uya_frame_count(void); const char *rc_stack_scan(void); uya_log("v%u WAD unpack %08X -> %08X: %.3s; stack%s", uya_frame_count(), (u32)(uintptr_t)src, (u32)(uintptr_t)dst, (const char *)src, rc_stack_scan()); }
|
||||
}
|
||||
if (src[0] != 'W' || src[1] != 'A' || src[2] != 'D') return 0;
|
||||
u32 size = src[3] | (src[4] << 8) | (src[5] << 16) | ((u32)src[6] << 24);
|
||||
u8 *data = src + 0x10, *in = data, *end = src + size;
|
||||
u32 flag, n;
|
||||
u8 *m;
|
||||
|
||||
if (in[0] > 0x11) { /* leading literal run */
|
||||
n = *in++ - 0x11;
|
||||
while (n--) *dst++ = *in++;
|
||||
goto after_literals;
|
||||
}
|
||||
for (;;) {
|
||||
/* state A: a literal run may come next */
|
||||
if (in >= end) break;
|
||||
flag = *in++;
|
||||
if (flag < 0x10) {
|
||||
n = flag ? flag : *in++ + 0xF;
|
||||
n += 3;
|
||||
while (n--) *dst++ = *in++;
|
||||
after_literals:
|
||||
if (in >= end) break;
|
||||
flag = *in++;
|
||||
}
|
||||
/* a match */
|
||||
for (;;) {
|
||||
if (flag >= 0x40) {
|
||||
m = dst - 1 - ((flag >> 2) & 7);
|
||||
m -= *in++ << 3;
|
||||
n = (flag >> 5) - 1;
|
||||
} else if (flag >= 0x20) {
|
||||
n = flag & 0x1F;
|
||||
if (!n) n = *in++ + 0x1F;
|
||||
m = dst - 1 - ((in[0] >> 2) + (in[1] << 6));
|
||||
in += 2;
|
||||
} else if (flag >= 0x10) {
|
||||
m = dst - ((flag & 8) << 11);
|
||||
n = flag & 7;
|
||||
if (!n) n = *in++ + 7;
|
||||
m -= (in[0] >> 2) + (in[1] << 6);
|
||||
in += 2;
|
||||
if (m == dst) {
|
||||
if (n != 1) { /* next-chunk marker */
|
||||
u32 off = (u32)(in - data);
|
||||
in = data + ((off + 0x1FFF) & ~0x1FFFu);
|
||||
goto state_a;
|
||||
}
|
||||
goto trailing; /* distance 0, length 1: no copy */
|
||||
}
|
||||
m -= 0x4000;
|
||||
} else {
|
||||
uya_unimpl("func_0020C468: bad WAD stream");
|
||||
return (s32)(dst - start);
|
||||
}
|
||||
*dst++ = *m++;
|
||||
*dst++ = *m++;
|
||||
do { *dst++ = *m++; } while ((s32)--n > 0);
|
||||
trailing:
|
||||
n = in[-2] & 3; /* up to 3 literals after a match */
|
||||
if (!n) goto state_a;
|
||||
while (n--) *dst++ = *in++;
|
||||
if (in >= end) return (s32)(dst - start);
|
||||
flag = *in++;
|
||||
}
|
||||
state_a:;
|
||||
}
|
||||
return (s32)(dst - start);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/* 0x0022DBE8: SoundMasterVolumeInit (sound.c, still assembly in the decomp).
|
||||
* Ported from the assembly: clears the sound tables at 0x0013E650, starts
|
||||
* 989snd, sets playback mode, reverb and voice ranges, derives the six master
|
||||
* volumes from the option values at 0x0015EEEC/0x0015EEF0, starts the music
|
||||
* streamer (func_002161E0, which also registers the CD callback the
|
||||
* streaming loads wait on) and sets the volumes. */
|
||||
#include "common.h"
|
||||
|
||||
extern void func_0012DB68(void); /* snd_StartSoundSystem */
|
||||
extern void func_0012E380(s32); /* snd_SetPlaybackMode */
|
||||
extern void func_0012E3C0(s32, s32);
|
||||
extern void func_0012F0E8(s32, s32); /* snd_PreAllocReverbWorkArea */
|
||||
extern void func_0012E3F8(s32, s32, s32); /* snd_SetGroupVoiceRange */
|
||||
extern void func_0012E348(s32, s32); /* snd_SetMasterVolume */
|
||||
extern void func_002161E0(void); /* MusicStreamInit */
|
||||
|
||||
void func_0022DBE8(void)
|
||||
{
|
||||
u8 *b = (u8 *)0x0013E650;
|
||||
s32 vol, v8, v7;
|
||||
__builtin_memset(b, 0, 0x40);
|
||||
*(u32 *)(b + 0x40) = 0;
|
||||
*(u32 *)(b + 0x44) = 0;
|
||||
for (s32 k = 1; k <= 30; k++) { *(u32 *)(b + 0x70 * k) = 0; b[0x70 * k + 4] = 0; }
|
||||
func_0012DB68();
|
||||
func_0012E380(*(s32 *)0x0015EEE8 == 0);
|
||||
func_0012E3C0(0, 1);
|
||||
func_0012F0E8(2, 4);
|
||||
func_0012E3F8(1, 0x18, 0x2F);
|
||||
func_0012E3F8(2, 0x18, 0x2F);
|
||||
func_0012E3F8(4, 0x18, 0x2F);
|
||||
vol = *(s32 *)0x0015EEF0;
|
||||
v8 = vol * 8 / 10;
|
||||
v7 = vol * 7 / 10;
|
||||
*(s32 *)(b + 0x4C) = *(s32 *)0x0015EEEC;
|
||||
*(s32 *)(b + 0x48) = v8;
|
||||
*(s32 *)(b + 0x50) = v8;
|
||||
*(s32 *)(b + 0x54) = v7;
|
||||
*(s32 *)(b + 0x58) = v7;
|
||||
*(s32 *)(b + 0x5C) = vol;
|
||||
func_002161E0();
|
||||
for (s32 i = 0; i < 6; i++) func_0012E348(i, *(s32 *)(b + 0x48 + 4 * i));
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/* 0x0023B670: play a movie (src/game/movie/movie.c): (sector, size, buffer,
|
||||
* player state, language). The Sony sample player it runs (IPU, threads,
|
||||
* SPU, uncached buffers) is replaced by the host's MPEG-2 player
|
||||
* (host/src/movie/player.c); the language picks the movie's sound track. */
|
||||
#include "common.h"
|
||||
int uya_movie_play_track(u32 lsn, u32 bytes, int track);
|
||||
s32 func_0023B670(s32 sector, s32 size, s32 buf, s32 state, s32 lang)
|
||||
{
|
||||
(void)buf; (void)state;
|
||||
u32 bytes = (u32)size < 0x40000u ? (u32)size * 2048u : (u32)size; /* the table gives sectors */
|
||||
uya_movie_play_track((u32)sector, bytes, lang);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/* GIF data from VIF1 DIRECT (PATH2): drawn by the software GS (host/src/gs) and counted here. */
|
||||
#include "uya_host.h"
|
||||
#include "uya_runtime.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
static unsigned long long prims[8], tags, regs_written[256], ad_addr[128], image_qw;
|
||||
|
||||
void uya_gs_gif(const u32 *d, u32 qwc);
|
||||
|
||||
void uya_gif_packet(const u32 *d, u32 qwc)
|
||||
{
|
||||
u32 q = 0;
|
||||
static int want = -1;
|
||||
uya_gs_gif(d, qwc);
|
||||
if (want < 0) want = getenv("UYA_GIFSTATS") != 0; /* testing: count tags and registers */
|
||||
if (!want) return;
|
||||
while (q < qwc) {
|
||||
const u32 *t = d + q * 4;
|
||||
u64 lo = ((u64)t[1] << 32) | t[0], hi = ((u64)t[3] << 32) | t[2];
|
||||
u32 nloop = lo & 0x7FFF, eop = (lo >> 15) & 1, pre = (lo >> 46) & 1, prim = (lo >> 47) & 0x7FF;
|
||||
u32 flg = (lo >> 58) & 3, nreg = (lo >> 60) & 0xF;
|
||||
if (!nreg) nreg = 16;
|
||||
tags++;
|
||||
if (pre) prims[prim & 7]++;
|
||||
q++;
|
||||
if (flg == 0) { /* PACKED: one qword per register per loop */
|
||||
for (u32 l = 0; l < nloop; l++)
|
||||
for (u32 r = 0; r < nreg; r++) {
|
||||
u32 rg = (hi >> (r * 4)) & 0xF;
|
||||
regs_written[rg]++;
|
||||
if (q < qwc) {
|
||||
const u32 *v = d + q * 4;
|
||||
if (rg == 0xE) { ad_addr[v[2] & 0x7F]++; if ((v[2] & 0x7F) == 0) prims[v[0] & 7]++; }
|
||||
if (rg == 0x0) prims[v[0] & 7]++;
|
||||
}
|
||||
q++;
|
||||
}
|
||||
} else if (flg == 1) { /* REGLIST: 64 bits per register */
|
||||
q += (nloop * nreg + 1) / 2;
|
||||
} else { /* IMAGE */
|
||||
q += nloop; image_qw += nloop;
|
||||
}
|
||||
if (eop) break;
|
||||
}
|
||||
}
|
||||
|
||||
void uya_gif_stats(void)
|
||||
{
|
||||
uya_log("GIF tags %llu; prims: point %llu line %llu linestrip %llu tri %llu tristrip %llu fan %llu sprite %llu",
|
||||
tags, prims[0], prims[1], prims[2], prims[3], prims[4], prims[5], prims[6]);
|
||||
uya_log("PACKED regs: %llu A+D, %llu RGBAQ, %llu ST, %llu UV, %llu XYZF2, %llu XYZ2; image qwords %llu",
|
||||
regs_written[0xE], regs_written[1], regs_written[2], regs_written[3], regs_written[4], regs_written[5], image_qw);
|
||||
for (int a = 0; a < 128; a++) if (ad_addr[a]) uya_log(" A+D GS reg 0x%02X: %llu", a, ad_addr[a]);
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
/* VIF0 and VIF1: the command streams DMA channels 0 and 1 (and the VIF FIFOs)
|
||||
* carry to the vector units.
|
||||
*
|
||||
* UNPACK writes VU data memory (every vn/vl format, signed/unsigned, the
|
||||
* STMASK/STROW/STCOL masking, the STCYCL write/skip cycles, the STMOD
|
||||
* offset/difference modes and VIF1's double buffering with BASE/OFFSET/TOPS
|
||||
* and the +TOPS flag). MPG uploads microcode (host/src/gfx/vu.c); MSCAL,
|
||||
* MSCALF and MSCNT run the translated microprogram there (tools/vu_recomp.py)
|
||||
* to completion: the VU programs end with XGKICKs that hand their GIF packets
|
||||
* to the GS. DIRECT/DIRECTHL carry GIF packets for the GS (PATH2). The flushes
|
||||
* have nothing to wait for, since everything before them has already run. */
|
||||
#include "uya_host.h"
|
||||
#include "uya_runtime.h"
|
||||
#include "vu.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void uya_gif_packet(const u32 *data, u32 qwc);
|
||||
void vu_mpg(int vu, u32 addr, const u32 *w, u32 ninstr);
|
||||
void vu_run(int vu, u32 pc);
|
||||
|
||||
typedef struct {
|
||||
int id;
|
||||
u32 cl, wl, mode, mask, row[4], col[4];
|
||||
u32 base, ofst, tops, top, itops, itop, dbf, mark;
|
||||
u32 code, need, got; /* the command whose data is still coming */
|
||||
u32 *buf, cap;
|
||||
} Vif;
|
||||
|
||||
static u32 buf0[0x1000], buf1[0x10000 * 4 + 16];
|
||||
static Vif vif[2] = { { .id = 0, .cl = 1, .wl = 1, .buf = buf0, .cap = sizeof buf0 / 4 },
|
||||
{ .id = 1, .cl = 1, .wl = 1, .buf = buf1, .cap = sizeof buf1 / 4 } };
|
||||
static unsigned long long cmd_count[128], frames;
|
||||
|
||||
static const char *cmd_name(u32 c)
|
||||
{
|
||||
switch (c) {
|
||||
case 0x00: return "NOP"; case 0x01: return "STCYCL"; case 0x02: return "OFFSET"; case 0x03: return "BASE";
|
||||
case 0x04: return "ITOP"; case 0x05: return "STMOD"; case 0x06: return "MSKPATH3"; case 0x07: return "MARK";
|
||||
case 0x10: return "FLUSHE"; case 0x11: return "FLUSH"; case 0x13: return "FLUSHA"; case 0x14: return "MSCAL";
|
||||
case 0x15: return "MSCALF"; case 0x17: return "MSCNT"; case 0x20: return "STMASK"; case 0x30: return "STROW";
|
||||
case 0x31: return "STCOL"; case 0x4A: return "MPG"; case 0x50: return "DIRECT"; case 0x51: return "DIRECTHL";
|
||||
}
|
||||
return (c & 0x60) == 0x60 ? "UNPACK" : "?";
|
||||
}
|
||||
|
||||
u32 vu_vif_top(int vu) { return vif[vu & 1].top; }
|
||||
u32 vu_vif_itop(int vu) { return vif[vu & 1].itop; }
|
||||
|
||||
/* vectors of an UNPACK of `num` writes that take data (the rest are filled) */
|
||||
static u32 data_vectors(const Vif *v, u32 num)
|
||||
{
|
||||
u32 cl = v->cl, wl = v->wl ? v->wl : 1;
|
||||
if (wl <= cl) return num;
|
||||
return (num / wl) * cl + (num % wl < cl ? num % wl : cl);
|
||||
}
|
||||
|
||||
static u32 unpack_words(const Vif *v, u32 cmd, u32 num)
|
||||
{
|
||||
u32 vn = (cmd >> 2) & 3, vl = cmd & 3;
|
||||
u32 bits = vl == 3 ? 16 : (32 >> vl) * (vn + 1);
|
||||
return (bits * data_vectors(v, num) + 31) / 32;
|
||||
}
|
||||
|
||||
/* element k (of the format's element size) of the data */
|
||||
static inline u32 elem(const u32 *d, u32 nwords, u32 vl, u32 k, int usn)
|
||||
{
|
||||
switch (vl) {
|
||||
case 0: return k < nwords ? d[k] : 0;
|
||||
case 1: { if (k / 2 >= nwords) return 0; u16 h = ((const u16 *)d)[k]; return usn ? h : (u32)(s32)(s16)h; }
|
||||
case 2: { if (k / 4 >= nwords) return 0; u8 b = ((const u8 *)d)[k]; return usn ? b : (u32)(s32)(s8)b; }
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* the usual UNPACK: no mask, every write takes data (WL <= CL); the format is
|
||||
* a constant here (see unpack()), so elem() comes down to a load */
|
||||
static inline __attribute__((always_inline)) void unpack_plain(Vif *v, u8 *mem, u32 qmask, u32 addr, u32 cl, u32 wl,
|
||||
const u32 *d, u32 nwords, u32 num, const u32 vn, const u32 vl, u32 usn)
|
||||
{
|
||||
for (u32 k = 0; k < num; k++) {
|
||||
u32 qa = wl == cl ? addr + k : addr + (k / wl) * cl + k % wl, val[4];
|
||||
u32 e = k * (vn + 1);
|
||||
switch (vn) {
|
||||
case 0: val[0] = val[1] = val[2] = val[3] = elem(d, nwords, vl, e, usn); break;
|
||||
case 1: val[0] = val[2] = elem(d, nwords, vl, e, usn); val[1] = val[3] = elem(d, nwords, vl, e + 1, usn); break;
|
||||
default: for (int c = 0; c < 4; c++) val[c] = elem(d, nwords, vl, e + c, usn); break;
|
||||
}
|
||||
u32 *dst = (u32 *)(mem + ((qa & qmask) << 4));
|
||||
if (v->mode == 0) { dst[0] = val[0]; dst[1] = val[1]; dst[2] = val[2]; dst[3] = val[3]; }
|
||||
else if (v->mode == 1) for (int c = 0; c < 4; c++) dst[c] = val[c] + v->row[c];
|
||||
else if (v->mode == 2) for (int c = 0; c < 4; c++) { v->row[c] += val[c]; dst[c] = v->row[c]; }
|
||||
else for (int c = 0; c < 4; c++) dst[c] = val[c];
|
||||
}
|
||||
}
|
||||
|
||||
static void unpack(Vif *v, u32 code, const u32 *d, u32 nwords)
|
||||
{
|
||||
u32 cmd = (code >> 24) & 0x7F, imm = code & 0xFFFF, num = (code >> 16) & 0xFF;
|
||||
if (!num) num = 256;
|
||||
u32 vn = (cmd >> 2) & 3, vl = cmd & 3, m = (cmd >> 4) & 1, usn = (imm >> 14) & 1;
|
||||
u32 qmask = v->id ? 0x3FF : 0xFF;
|
||||
u32 addr = imm & 0x3FF;
|
||||
if (v->id && (imm & 0x8000)) addr += v->tops;
|
||||
u8 *mem = (u8 *)(uintptr_t)(v->id ? 0x1100C000u : 0x11004000u);
|
||||
u32 cl = v->cl, wl = v->wl ? v->wl : 1, di = 0;
|
||||
if (!m && wl <= cl && vl != 3) {
|
||||
switch (vn * 4 + vl) {
|
||||
#define U(n, l) case n * 4 + l: unpack_plain(v, mem, qmask, addr, cl, wl, d, nwords, num, n, l, usn); return;
|
||||
U(0, 0) U(0, 1) U(0, 2) U(1, 0) U(1, 1) U(1, 2) U(2, 0) U(2, 1) U(2, 2) U(3, 0) U(3, 1) U(3, 2)
|
||||
#undef U
|
||||
}
|
||||
}
|
||||
for (u32 k = 0; k < num; k++) {
|
||||
u32 pos = k % wl, qa, has;
|
||||
if (wl <= cl) { qa = addr + (k / wl) * cl + pos; has = 1; }
|
||||
else { qa = addr + k; has = pos < cl; }
|
||||
u32 val[4] = { 0, 0, 0, 0 };
|
||||
if (has) {
|
||||
if (vl == 3) { /* V4-5: RGBA 5:5:5:1 */
|
||||
u32 h = di / 2 < nwords ? ((const u16 *)d)[di] : 0;
|
||||
val[0] = (h & 0x1F) << 3; val[1] = ((h >> 5) & 0x1F) << 3; val[2] = ((h >> 10) & 0x1F) << 3; val[3] = ((h >> 15) & 1) << 7;
|
||||
} else {
|
||||
u32 e = di * (vn + 1);
|
||||
switch (vn) {
|
||||
case 0: val[0] = val[1] = val[2] = val[3] = elem(d, nwords, vl, e, usn); break;
|
||||
case 1: val[0] = val[2] = elem(d, nwords, vl, e, usn); val[1] = val[3] = elem(d, nwords, vl, e + 1, usn); break;
|
||||
case 2: for (int c = 0; c < 4; c++) val[c] = elem(d, nwords, vl, e + c, usn); break; /* w: the next element */
|
||||
case 3: for (int c = 0; c < 4; c++) val[c] = elem(d, nwords, vl, e + c, usn); break;
|
||||
}
|
||||
}
|
||||
di++;
|
||||
}
|
||||
u32 *dst = (u32 *)(mem + ((qa & qmask) << 4));
|
||||
u32 r = pos < 3 ? pos : 3;
|
||||
for (int c = 0; c < 4; c++) {
|
||||
u32 mk = m ? (v->mask >> ((r * 4 + c) * 2)) & 3 : (has ? 0 : 1);
|
||||
switch (mk) {
|
||||
case 0:
|
||||
if (v->mode == 1) dst[c] = val[c] + v->row[c];
|
||||
else if (v->mode == 2) { v->row[c] += val[c]; dst[c] = v->row[c]; }
|
||||
else dst[c] = val[c];
|
||||
break;
|
||||
case 1: dst[c] = v->row[c]; break;
|
||||
case 2: dst[c] = v->col[r]; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void start(Vif *v, u32 pc)
|
||||
{
|
||||
if (v->id) {
|
||||
v->top = v->tops;
|
||||
if (v->dbf) { v->tops = v->base; v->dbf = 0; }
|
||||
else { v->tops = (v->base + v->ofst) & 0x3FF; v->dbf = 1; }
|
||||
}
|
||||
v->itop = v->itops;
|
||||
vu_run(v->id, pc);
|
||||
}
|
||||
|
||||
static void bad_cmd(Vif *v, u32 code)
|
||||
{
|
||||
static u32 seen[2][4];
|
||||
u32 c = (code >> 24) & 0x7F;
|
||||
if (seen[v->id][c / 32] & (1u << (c % 32))) return;
|
||||
seen[v->id][c / 32] |= 1u << (c % 32);
|
||||
uya_log("VIF%d: command 0x%02X (%08X) not handled", v->id, c, code);
|
||||
}
|
||||
|
||||
/* run a command once all its data (d, n words) is there */
|
||||
int uya_vif_log; /* testing: UYA_DMALOG=n also lists send n's VIF commands */
|
||||
|
||||
static u32 hist[64][3], hist_n; /* testing: the last VIF1 commands (code, TOPS, first data word) */
|
||||
void uya_vif1_dump_hist(void)
|
||||
{
|
||||
uya_log("VIF1: BASE %03X OFFSET %03X TOPS %03X TOP %03X DBF %u ITOP %03X; last commands:", vif[1].base, vif[1].ofst, vif[1].tops, vif[1].top, vif[1].dbf, vif[1].itop);
|
||||
for (u32 i = 0; i < 64; i++) { u32 k = (hist_n + i) & 63; if (hist[k][0] || hist[k][1]) uya_log(" %-8s %08X tops %03X data0 %08X", cmd_name((hist[k][0] >> 24) & 0x7F), hist[k][0], hist[k][1], hist[k][2]); }
|
||||
}
|
||||
|
||||
static void exec(Vif *v, u32 code, const u32 *d, u32 n)
|
||||
{
|
||||
u32 cmd = (code >> 24) & 0x7F, imm = code & 0xFFFF, num = (code >> 16) & 0xFF;
|
||||
if (v->id) { cmd_count[cmd]++; hist[hist_n & 63][0] = code; hist[hist_n & 63][1] = v->tops; hist[hist_n & 63][2] = n ? d[0] : 0; hist_n++; }
|
||||
if (uya_vif_log) uya_log(" VIF%d %-8s %08X (%u data words)", v->id, cmd_name(cmd), code, n);
|
||||
switch (cmd) {
|
||||
case 0x00: case 0x06: case 0x10: case 0x11: case 0x13: break;
|
||||
case 0x01: v->cl = imm & 0xFF; v->wl = (imm >> 8) & 0xFF; break;
|
||||
case 0x02: v->ofst = imm & 0x3FF; v->dbf = 0; v->tops = v->base; break;
|
||||
case 0x03: v->base = imm & 0x3FF; break;
|
||||
case 0x04: v->itops = imm & 0x3FF; break;
|
||||
case 0x05: v->mode = imm & 3; break;
|
||||
case 0x07: v->mark = imm; break;
|
||||
case 0x14: case 0x15: start(v, imm * 8); break;
|
||||
case 0x17: start(v, v->id ? rc_vu1.tpc : rc_vu0.tpc); break;
|
||||
case 0x20: v->mask = d[0]; break;
|
||||
case 0x30: memcpy(v->row, d, 16); break;
|
||||
case 0x31: memcpy(v->col, d, 16); break;
|
||||
case 0x4A: vu_mpg(v->id, imm * 8, d, num ? num : 256); break;
|
||||
case 0x50: case 0x51:
|
||||
if (v->id) {
|
||||
extern const char *uya_gs_src; uya_gs_src = "PATH2 (VIF1 DIRECT)";
|
||||
static int left = -1; /* testing: UYA_BADTEX=n logs DIRECT packets with a TEX0 whose top half looks like a pointer */
|
||||
if (left < 0) { const char *e = getenv("UYA_BADTEXV"); left = e ? atoi(e) : 0; }
|
||||
if (left > 0)
|
||||
for (u32 k = 0; k + 3 < n; k += 4)
|
||||
if ((d[k + 2] & 0xFF) == 6 && (d[k + 1] >> 16) >= 0xFF00) {
|
||||
left--;
|
||||
uya_log("DIRECT packet at %08X (%u qwords) has TEX0 %08X%08X at qword %u:", (u32)(uintptr_t)d, n / 4, d[k + 1], d[k], k / 4);
|
||||
for (u32 j = 0; j < n && j < 64; j += 4) uya_log(" %08X: %08X %08X %08X %08X", (u32)(uintptr_t)(d + j), d[j], d[j + 1], d[j + 2], d[j + 3]);
|
||||
break;
|
||||
}
|
||||
uya_gif_packet(d, n / 4);
|
||||
}
|
||||
else bad_cmd(v, code);
|
||||
break;
|
||||
default:
|
||||
if ((cmd & 0x60) == 0x60) unpack(v, code, d, n);
|
||||
else bad_cmd(v, code);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* words of data that follow a command */
|
||||
static u32 data_words(Vif *v, u32 code)
|
||||
{
|
||||
u32 cmd = (code >> 24) & 0x7F, imm = code & 0xFFFF, num = (code >> 16) & 0xFF;
|
||||
switch (cmd) {
|
||||
case 0x20: return 1;
|
||||
case 0x30: case 0x31: return 4;
|
||||
case 0x4A: return (num ? num : 256) * 2;
|
||||
case 0x50: case 0x51: return (imm ? imm : 0x10000) * 4;
|
||||
}
|
||||
if ((cmd & 0x60) == 0x60) return unpack_words(v, cmd, num ? num : 256);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void feed(Vif *v, const u32 *w, u32 n)
|
||||
{
|
||||
u32 i = 0;
|
||||
while (i < n) {
|
||||
if (v->need) {
|
||||
u32 take = v->need < n - i ? v->need : n - i;
|
||||
if (!v->got && take == v->need) { /* all of it here: no copy */
|
||||
v->need = 0;
|
||||
exec(v, v->code, w + i, take);
|
||||
i += take;
|
||||
continue;
|
||||
}
|
||||
for (u32 k = 0; k < take; k++) if (v->got + k < v->cap) v->buf[v->got + k] = w[i + k];
|
||||
v->got += take; v->need -= take; i += take;
|
||||
if (!v->need) { u32 g = v->got < v->cap ? v->got : v->cap; v->got = 0; exec(v, v->code, v->buf, g); }
|
||||
continue;
|
||||
}
|
||||
u32 code = w[i++];
|
||||
u32 nd = data_words(v, code);
|
||||
if (!nd) { exec(v, code, 0, 0); continue; }
|
||||
v->code = code; v->need = nd; v->got = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void uya_vif0_words(const u32 *w, u32 n) { feed(&vif[0], w, n); }
|
||||
void uya_vif0_data(const u32 *w, u32 qwc) { feed(&vif[0], w, qwc * 4); }
|
||||
void uya_vif1_words(const u32 *w, u32 n) { feed(&vif[1], w, n); }
|
||||
void uya_vif1_data(const u32 *w, u32 qwc) { feed(&vif[1], w, qwc * 4); }
|
||||
|
||||
void uya_gfx_frame_stats(void)
|
||||
{
|
||||
frames++;
|
||||
if (frames == 3 || frames == 600 || frames == 3000) {
|
||||
void vu_stats(void);
|
||||
uya_log("VIF1 command counts after %llu sends:", frames);
|
||||
for (int c = 0; c < 128; c++)
|
||||
if (cmd_count[c]) uya_log(" %-8s %llu", cmd_name(c), cmd_count[c]);
|
||||
vu_stats();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
/* VU0/VU1 micro mode: which translated microprogram is in micro memory, and
|
||||
* running it.
|
||||
*
|
||||
* The game uploads microcode with MPG (VIF0 for VU0, VIF1 for VU1). The bytes
|
||||
* land in micro memory at their real address (VU0 0x11000000, 4 KB; VU1
|
||||
* 0x11008000, 16 KB). tools/vu_recomp.py translated every microprogram image
|
||||
* in the executable's .vutext to C at build time and kept its bytes: after an
|
||||
* upload, every known upload block that overlaps it is compared with micro
|
||||
* memory, so a start address (vcallms / MSCAL / MSCNT) maps to the image whose
|
||||
* blocks are all in place. Microcode that matches nothing is reported once
|
||||
* with its address and a hash (UYA_VUDUMP=dir also saves it), so it can be
|
||||
* added to the build. */
|
||||
#include "uya_host.h"
|
||||
#include "uya_runtime.h"
|
||||
#include "vu.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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;
|
||||
extern const VuImage vu_images[];
|
||||
extern const int vu_image_count;
|
||||
|
||||
RcVu0 rc_vu1 = { .vf = { [0] = {0, 0, 0, 1} } };
|
||||
|
||||
#define MAX_IMG 64
|
||||
static u8 blk_ok[MAX_IMG][16]; /* block b of image i is in micro memory */
|
||||
static int cur_img[2][0x4000 / 8]; /* image last found for a start address, -1 none */
|
||||
static int cache_valid[2];
|
||||
|
||||
static u8 *micro(int vu) { return (u8 *)(uintptr_t)(vu ? 0x11008000u : 0x11000000u); }
|
||||
u32 vu_nonfinite; /* VU FMAC results that overflowed to inf/NaN (made +-MAX: vu_fc, vu.h) */
|
||||
static u32 micro_size(int vu) { return vu ? 0x4000 : 0x1000; }
|
||||
|
||||
void vu_bad(int vu, u32 pc, const char *what)
|
||||
{
|
||||
static int n;
|
||||
if (n++ < 32) uya_log("VU%d microprogram: %s at 0x%04X", vu, what, pc);
|
||||
}
|
||||
|
||||
static u32 fnv(const u8 *p, u32 n) { u32 h = 2166136261u; while (n--) { h ^= *p++; h *= 16777619u; } return h; }
|
||||
|
||||
/* MPG: ninstr instructions to micro memory at byte address addr */
|
||||
void vu_mpg(int vu, u32 addr, const u32 *w, u32 ninstr)
|
||||
{
|
||||
u8 *m = micro(vu);
|
||||
u32 msz = micro_size(vu), bytes = ninstr * 8;
|
||||
for (u32 i = 0; i < ninstr; i++) memcpy(m + ((addr + i * 8) & (msz - 1)), w + i * 2, 8);
|
||||
/* re-check every known block that overlaps the upload */
|
||||
int covered = 0;
|
||||
for (int i = 0; i < vu_image_count && i < MAX_IMG; i++) {
|
||||
const VuImage *im = &vu_images[i];
|
||||
if (im->vu != vu) continue;
|
||||
for (int b = 0; b < im->nblk && b < 16; b++) {
|
||||
const VuBlock *bl = &im->blk[b];
|
||||
if (bl->vma < addr + bytes && addr < bl->vma + bl->size) {
|
||||
blk_ok[i][b] = memcmp(m + bl->vma, bl->words, bl->size) == 0;
|
||||
if (blk_ok[i][b] && bl->vma <= addr && addr + bytes <= bl->vma + bl->size) covered = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
cache_valid[vu] = 0;
|
||||
if (!covered) {
|
||||
static u32 seen[64]; static int nseen;
|
||||
u32 h = fnv((const u8 *)w, bytes);
|
||||
int k;
|
||||
for (k = 0; k < nseen && seen[k] != h; k++) ;
|
||||
if (k == nseen) {
|
||||
if (nseen < 64) seen[nseen++] = h;
|
||||
uya_log("VU%d: microcode uploaded at 0x%04X (%u instructions, hash %08X) is not one the build translated", vu, addr, ninstr, h);
|
||||
const char *dir = getenv("UYA_VUDUMP");
|
||||
if (dir) {
|
||||
char path[512]; snprintf(path, sizeof path, "%s/vu%d_%04X_%08X.bin", dir, vu, addr, h);
|
||||
FILE *f = fopen(path, "wb"); if (f) { fwrite(w, 1, bytes, f); fclose(f); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The image to run from pc: the one whose upload containing pc is in micro
|
||||
* memory, preferring one that is there completely (a later upload may have
|
||||
* replaced a part of an image the program at pc never reaches, e.g. image
|
||||
* 28259's stub at 0x0000 under the 0x0000-0x0580 one). */
|
||||
static int find_image(int vu, u32 pc)
|
||||
{
|
||||
if (!cache_valid[vu]) { memset(cur_img[vu], 0xFF, sizeof cur_img[vu]); cache_valid[vu] = 1; }
|
||||
int *slot = &cur_img[vu][(pc & (micro_size(vu) - 1)) / 8];
|
||||
if (*slot >= 0) return *slot;
|
||||
int part = -1;
|
||||
for (int i = 0; i < vu_image_count && i < MAX_IMG; i++) {
|
||||
const VuImage *im = &vu_images[i];
|
||||
if (im->vu != vu) continue;
|
||||
int has = 0, ok = 1;
|
||||
for (int b = 0; b < im->nblk && b < 16; b++) {
|
||||
const VuBlock *bl = &im->blk[b];
|
||||
if (pc >= bl->vma && pc < bl->vma + bl->size && blk_ok[i][b]) has = 1;
|
||||
if (!blk_ok[i][b]) ok = 0;
|
||||
}
|
||||
if (has && ok) { *slot = i; return i; }
|
||||
if (has && part < 0) part = i;
|
||||
}
|
||||
return *slot = part;
|
||||
}
|
||||
|
||||
static unsigned long long runs[2], kicks, kick_qw;
|
||||
static unsigned long long img_runs[MAX_IMG];
|
||||
static u32 *pc_runs[MAX_IMG]; /* runs per start address, for the statistics */
|
||||
static const char *cur_name = ""; static u32 cur_pc;
|
||||
|
||||
/* Testing: UYA_VUCAPTURE=image,n saves the state (start address, registers,
|
||||
* data memory) before and after the n-th run of that image to
|
||||
* vucap_before.bin / vucap_after.bin in the working directory, to replay it
|
||||
* against a reference interpreter (tools/vu_refcheck.py). The file ends
|
||||
* with VIF TOP and ITOP. */
|
||||
static void capture(const char *path, int vu, u32 pc, const RcVu0 *v)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return;
|
||||
fwrite(&pc, 4, 1, f); fwrite(v, sizeof *v, 1, f);
|
||||
fwrite((void *)(uintptr_t)(vu ? 0x1100C000u : 0x11004000u), 1, vu ? 0x4000 : 0x1000, f);
|
||||
u32 tops[2] = { vu_vif_top(vu), vu_vif_itop(vu) };
|
||||
fwrite(tops, 4, 2, f);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
static void xgkick_flush(void);
|
||||
unsigned long long uya_t_vu;
|
||||
static void vu_run_(int vu, u32 pc);
|
||||
extern int uya_timing; /* host/src/gs/gs.c: UYA_FPSLOG */
|
||||
void vu_run(int vu, u32 pc)
|
||||
{
|
||||
if (uya_timing <= 0) { vu_run_(vu, pc); return; }
|
||||
unsigned lo, hi, lo2, hi2; __asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));
|
||||
vu_run_(vu, pc);
|
||||
__asm__ volatile("rdtsc" : "=a"(lo2), "=d"(hi2));
|
||||
uya_t_vu += (((unsigned long long)hi2 << 32) | lo2) - (((unsigned long long)hi << 32) | lo);
|
||||
}
|
||||
static void vu_run_(int vu, u32 pc)
|
||||
{
|
||||
RcVu0 *v = vu ? &rc_vu1 : &rc_vu0;
|
||||
pc &= micro_size(vu) - 8;
|
||||
int i = find_image(vu, pc);
|
||||
if (i < 0) {
|
||||
static u32 seen[2][0x4000 / 8 / 32];
|
||||
u32 k = pc / 8;
|
||||
if (!(seen[vu][k / 32] & (1u << (k % 32)))) {
|
||||
seen[vu][k / 32] |= 1u << (k % 32);
|
||||
uya_log("VU%d: no translated microprogram for start address 0x%04X", vu, pc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
runs[vu]++;
|
||||
img_runs[i]++;
|
||||
if (!pc_runs[i]) pc_runs[i] = calloc(0x800, sizeof(u32));
|
||||
if (pc_runs[i]) pc_runs[i][(pc / 8) & 0x7FF]++;
|
||||
cur_name = vu_images[i].name; cur_pc = pc;
|
||||
static int want = -2, seen; static char wname[32]; static unsigned wpc = 0xFFFFFFFFu;
|
||||
if (want == -2) { const char *e = getenv("UYA_VUCAPTURE"); want = -1; if (e && sscanf(e, "%31[^,],%d,%x", wname, &want, &wpc) < 2) want = -1; }
|
||||
if (want >= 0 && !strcmp(wname, vu_images[i].name) && (wpc == 0xFFFFFFFFu || wpc == pc) && seen++ == want) {
|
||||
capture("vucap_before.bin", vu, pc, v);
|
||||
vu_images[i].fn(v, pc);
|
||||
if (vu) xgkick_flush();
|
||||
capture("vucap_after.bin", vu, pc, v);
|
||||
uya_log("VU capture of image %s run %d (from 0x%04X) saved", wname, want, pc);
|
||||
return;
|
||||
}
|
||||
vu_images[i].fn(v, pc);
|
||||
if (vu) xgkick_flush();
|
||||
}
|
||||
|
||||
/* VU1 XGKICK: the GIF packet at quadword qaddr of VU1 data memory goes to the
|
||||
* GS (PATH1). The packet is complete when the program kicks it. */
|
||||
void uya_gif_packet(const u32 *data, u32 qwc);
|
||||
static u32 kbuf[0x400 * 4], kbuf_n, kbuf_q;
|
||||
void uya_vu_dump_kick(void)
|
||||
{
|
||||
void uya_vif1_dump_hist(void);
|
||||
uya_log("current XGKICK: qw %03X, %u qwords, image %s from %04X; vi:", kbuf_q, kbuf_n, cur_name, cur_pc);
|
||||
for (int k = 0; k < 16; k += 4) uya_log(" vi%-2d %04X %04X %04X %04X", k, rc_vu1.vi[k] & 0xFFFF, rc_vu1.vi[k + 1] & 0xFFFF, rc_vu1.vi[k + 2] & 0xFFFF, rc_vu1.vi[k + 3] & 0xFFFF);
|
||||
for (u32 k = 0; k < kbuf_n && k < 48; k++) uya_log(" %03X: %08X %08X %08X %08X", (kbuf_q + k) & 0x3FF, kbuf[k * 4], kbuf[k * 4 + 1], kbuf[k * 4 + 2], kbuf[k * 4 + 3]);
|
||||
uya_vif1_dump_hist();
|
||||
}
|
||||
/* XGKICK starts the transfer; the GIF reads the packet while the program
|
||||
* goes on, and programs rely on that: they finish the packet in the kick's
|
||||
* own branch delay slot or end-of-program slot (image 221571 writes the last
|
||||
* GIF tag's EOP there). So a kick is sent when the next kick comes or the
|
||||
* microprogram ends. */
|
||||
static u32 kick_pending = 0xFFFFFFFFu;
|
||||
static void xgkick_send(u32 qaddr);
|
||||
void vu_xgkick(u32 qaddr)
|
||||
{
|
||||
if (kick_pending != 0xFFFFFFFFu) xgkick_send(kick_pending);
|
||||
kick_pending = qaddr & 0x3FF;
|
||||
}
|
||||
static void xgkick_flush(void)
|
||||
{
|
||||
if (kick_pending != 0xFFFFFFFFu) { u32 q = kick_pending; kick_pending = 0xFFFFFFFFu; xgkick_send(q); }
|
||||
}
|
||||
static void xgkick_send(u32 qaddr)
|
||||
{
|
||||
u32 *buf = kbuf;
|
||||
const u32 *mem = (const u32 *)(uintptr_t)0x1100C000u;
|
||||
u32 q = qaddr & 0x3FF, n = 0;
|
||||
while (n < 0x400) {
|
||||
const u32 *t = mem + q * 4;
|
||||
u64 lo = ((u64)t[1] << 32) | t[0];
|
||||
u32 nloop = lo & 0x7FFF, eop = (lo >> 15) & 1, flg = (lo >> 58) & 3, nreg = (lo >> 60) & 15;
|
||||
if (!nreg) nreg = 16;
|
||||
u32 len = 1 + (flg == 0 ? nloop * nreg : flg == 1 ? (nloop * nreg + 1) / 2 : nloop);
|
||||
for (u32 k = 0; k < len && n < 0x400; k++) { memcpy(buf + n * 4, mem + ((q + k) & 0x3FF) * 4, 16); n++; }
|
||||
q = (q + len) & 0x3FF;
|
||||
if (eop) break;
|
||||
}
|
||||
kicks++; kick_qw += n; kbuf_n = n; kbuf_q = qaddr & 0x3FF;
|
||||
{
|
||||
static int badleft = 2;
|
||||
const u32 *t0 = mem + (qaddr & 0x3FF) * 4;
|
||||
if (badleft > 0 && !(t0[0] | t0[1] | t0[2] | t0[3])) {
|
||||
void uya_vif1_dump_hist(void);
|
||||
badleft--;
|
||||
uya_log("XGKICK of an empty GIF tag at qw %03X (image %s from %04X); vi:", qaddr & 0x3FF, cur_name, cur_pc);
|
||||
for (int k = 0; k < 16; k += 4) uya_log(" vi%-2d %04X %04X %04X %04X", k, rc_vu1.vi[k] & 0xFFFF, rc_vu1.vi[k + 1] & 0xFFFF, rc_vu1.vi[k + 2] & 0xFFFF, rc_vu1.vi[k + 3] & 0xFFFF);
|
||||
uya_vif1_dump_hist();
|
||||
}
|
||||
}
|
||||
static int left = -1; /* testing: UYA_KICKLOG=n lists the first n packets */
|
||||
if (left < 0) { const char *e = getenv("UYA_KICKLOG"); left = e ? atoi(e) : 0; }
|
||||
if (left > 0) {
|
||||
left--;
|
||||
uya_log("XGKICK at qw 0x%03X, %u qwords, image %s from 0x%04X:", qaddr & 0x3FF, n, cur_name, cur_pc);
|
||||
for (u32 k = 0; k < n && k < 24; k++) uya_log(" %08X %08X %08X %08X", buf[k * 4], buf[k * 4 + 1], buf[k * 4 + 2], buf[k * 4 + 3]);
|
||||
}
|
||||
{ /* "PATH1 <image>@<pc> kick <qw>" (snprintf's format, by hand: there are thousands of kicks a frame) */
|
||||
extern const char *uya_gs_src; static char src[64];
|
||||
static const char hx[] = "0123456789ABCDEF";
|
||||
char *o = src; const char *nm = cur_name;
|
||||
memcpy(o, "PATH1 ", 6); o += 6;
|
||||
while (*nm && o < src + 40) *o++ = *nm++;
|
||||
*o++ = '@';
|
||||
for (int sh = 12; sh >= 0; sh -= 4) *o++ = hx[(cur_pc >> sh) & 15];
|
||||
memcpy(o, " kick ", 6); o += 6;
|
||||
for (int sh = 8; sh >= 0; sh -= 4) *o++ = hx[((qaddr & 0x3FF) >> sh) & 15];
|
||||
*o = 0;
|
||||
uya_gs_src = src;
|
||||
}
|
||||
uya_gif_packet(buf, n);
|
||||
}
|
||||
|
||||
void vu_stats(void)
|
||||
{
|
||||
uya_log("VU microprograms run: VU0 %llu, VU1 %llu; XGKICK %llu (%llu qwords)", runs[0], runs[1], kicks, kick_qw);
|
||||
for (int i = 0; i < vu_image_count && i < MAX_IMG; i++) {
|
||||
if (!img_runs[i]) continue;
|
||||
char line[256]; int o = 0;
|
||||
for (int k = 0; k < 0x800 && o < 200; k++)
|
||||
if (pc_runs[i] && pc_runs[i][k]) o += snprintf(line + o, sizeof line - o, " %04X:%u", k * 8, pc_runs[i][k]);
|
||||
line[o] = 0;
|
||||
uya_log(" image %s (VU%d): %llu runs, by start address%s", vu_images[i].name, vu_images[i].vu, img_runs[i], line);
|
||||
}
|
||||
}
|
||||
|
||||
/* the EE's vcallms / vcallmsr */
|
||||
void rc_vcallms(RcCtx *c, u32 addr)
|
||||
{
|
||||
(void)c;
|
||||
rc_vu0.susp = 0; /* a new program replaces a parked one */
|
||||
vu_run(0, addr);
|
||||
}
|
||||
|
||||
/* The EE wrote a VI register (ctc2): a VU0 program parked in a wait loop for
|
||||
* it carries on until it ends or parks again. */
|
||||
void rc_vu0_resume(void)
|
||||
{
|
||||
if (!rc_vu0.susp) return;
|
||||
rc_vu0.susp = 0;
|
||||
vu_run(0, rc_vu0.tpc);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,56 @@
|
||||
/* gs_gl.h: the GPU (OpenGL 3.3 core) renderer beside the software GS.
|
||||
*
|
||||
* gs.c keeps the GIF front end, the registers, GS local memory, transfers,
|
||||
* CLUTs and the texture cache; with gsgl_on its primitives go to gs_gl.c,
|
||||
* which draws them on the GPU into GL textures standing for GS frame and Z
|
||||
* buffers (targets), and keeps GS memory and the targets coherent: whatever
|
||||
* the CPU side reads or writes in memory a target covers is synced first
|
||||
* (gsgl_cpu_read / gsgl_cpu_write). All calls are made on the thread that
|
||||
* owns the GL context (the GS thread with MTGS). */
|
||||
#ifndef GS_GL_H
|
||||
#define GS_GL_H
|
||||
#include "gs_types.h"
|
||||
|
||||
extern int gsgl_on; /* the GL renderer draws */
|
||||
|
||||
/* the display circuits' picture (from PMODE, SMODE2, DISPFB, DISPLAY) */
|
||||
typedef struct { u32 fbp, fbw, psm, dbx, dby, w, h; s32 shx, shy; } gs_disp_t;
|
||||
|
||||
int gsgl_init(u32 *vram); /* with a current GL 3.3 context: 1 if the renderer can run */
|
||||
void gsgl_prim(const pcmd_t *st, u32 st_index, const pprim_t *pr);
|
||||
void gsgl_flush(void); /* draw the batched primitives */
|
||||
|
||||
/* textures (cmd_setup): 1 if the texture is read from a GPU target directly
|
||||
* (p->rt set); else whether a level's memory is newer on the GPU (it is then
|
||||
* decoded only if a batch needs it: gs_tex_late); a decoded level in use */
|
||||
int gsgl_tex_setup(pcmd_t *p);
|
||||
int gsgl_tex_gpu(u32 psm, u32 tbp, u32 tbw, u32 tw, u32 th);
|
||||
void gsgl_tex_touch(const tlevel_t *l);
|
||||
void gsgl_clut_changed(void);
|
||||
|
||||
/* GS memory the CPU side is about to read or write (a rectangle of a buffer,
|
||||
* or a range of 256-byte blocks) */
|
||||
void gsgl_cpu_read_rect(u32 psm, u32 bp, u32 bw, u32 x, u32 y, u32 w, u32 h);
|
||||
void gsgl_cpu_write_rect(u32 psm, u32 bp, u32 bw, u32 x, u32 y, u32 w, u32 h);
|
||||
void gsgl_cpu_read(u32 bp, u32 nblocks);
|
||||
int gsgl_threaded(void);
|
||||
void gsgl_thread_times(double *busy, double *wait); /* GL calls go to the GL thread (it owns the context) */
|
||||
int gsgl_tex_owned(u32 psm, u32 tbp, u32 tbw, u32 tw, u32 th);
|
||||
void gsgl_tex_read(u32 psm, u32 tbp, u32 tbw, u32 tw, u32 th);
|
||||
|
||||
/* the display: to the window (and swap), or read back as BGRX (testing) */
|
||||
void gsgl_present(const gs_disp_t *d);
|
||||
void gsgl_present_bgra(const u32 *px, int w, int h); /* an FMV frame */
|
||||
int gsgl_capture(const gs_disp_t *d, u32 *out);
|
||||
void gsgl_stats(int nvsyncs); /* counts per vsync since the last call */
|
||||
|
||||
/* gs.c: decode a level of a state's texture now */
|
||||
int gs_tex_late(const pcmd_t *p, int lvl, tlevel_t *out);
|
||||
|
||||
/* the platform: a GL 3.3 core context for the window */
|
||||
int plat_gl_init(void); /* creates it, current on the calling thread */
|
||||
void plat_gl_current(int on); /* make it current on / release it from this thread */
|
||||
void plat_gl_swap(void);
|
||||
void plat_gl_size(int *w, int *h); /* the window's drawable size */
|
||||
extern int plat_gl_active; /* presenting with GL (no GDI drawing) */
|
||||
#endif
|
||||
@@ -0,0 +1,193 @@
|
||||
/* gs_gl_api.h: the OpenGL 3.3 core declarations the GL renderer uses. No
|
||||
* system GL headers: the entry points are loaded at run time through
|
||||
* plat_gl_proc (wglGetProcAddress on Windows, eglGetProcAddress in the
|
||||
* Linux test harness), so nothing links against a GL library. */
|
||||
#ifndef GS_GL_API_H
|
||||
#define GS_GL_API_H
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define GLAPIENTRY __stdcall
|
||||
#else
|
||||
#define GLAPIENTRY
|
||||
#endif
|
||||
|
||||
typedef unsigned int GLenum, GLuint, GLbitfield;
|
||||
typedef int GLint, GLsizei;
|
||||
typedef unsigned char GLboolean, GLubyte;
|
||||
typedef float GLfloat;
|
||||
typedef double GLdouble;
|
||||
typedef char GLchar;
|
||||
typedef ptrdiff_t GLsizeiptr, GLintptr;
|
||||
typedef struct __GLsync *GLsync;
|
||||
typedef uint64_t GLuint64;
|
||||
|
||||
#define GL_NONE_ 0
|
||||
#define GL_FALSE 0
|
||||
#define GL_TRUE 1
|
||||
#define GL_NO_ERROR 0
|
||||
#define GL_ZERO 0
|
||||
#define GL_ONE 1
|
||||
#define GL_POINTS 0x0000
|
||||
#define GL_LINES 0x0001
|
||||
#define GL_TRIANGLES 0x0004
|
||||
#define GL_NEVER 0x0200
|
||||
#define GL_LESS 0x0201
|
||||
#define GL_EQUAL 0x0202
|
||||
#define GL_LEQUAL 0x0203
|
||||
#define GL_GREATER 0x0204
|
||||
#define GL_NOTEQUAL 0x0205
|
||||
#define GL_GEQUAL 0x0206
|
||||
#define GL_ALWAYS 0x0207
|
||||
#define GL_SRC_COLOR 0x0300
|
||||
#define GL_ONE_MINUS_SRC_COLOR 0x0301
|
||||
#define GL_SRC_ALPHA 0x0302
|
||||
#define GL_ONE_MINUS_SRC_ALPHA 0x0303
|
||||
#define GL_DST_ALPHA 0x0304
|
||||
#define GL_ONE_MINUS_DST_ALPHA 0x0305
|
||||
#define GL_CONSTANT_ALPHA 0x8003
|
||||
#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004
|
||||
#define GL_SRC1_ALPHA 0x8589
|
||||
#define GL_ONE_MINUS_SRC1_ALPHA 0x88FB
|
||||
#define GL_FUNC_ADD 0x8006
|
||||
#define GL_FUNC_SUBTRACT 0x800A
|
||||
#define GL_FUNC_REVERSE_SUBTRACT 0x800B
|
||||
#define GL_DEPTH_TEST 0x0B71
|
||||
#define GL_BLEND 0x0BE2
|
||||
#define GL_SCISSOR_TEST 0x0C11
|
||||
#define GL_CULL_FACE 0x0B44
|
||||
#define GL_DITHER 0x0BD0
|
||||
#define GL_UNPACK_ALIGNMENT 0x0CF5
|
||||
#define GL_PACK_ALIGNMENT 0x0D05
|
||||
#define GL_UNPACK_ROW_LENGTH 0x0CF2
|
||||
#define GL_PACK_ROW_LENGTH 0x0D02
|
||||
#define GL_TEXTURE_2D 0x0DE1
|
||||
#define GL_UNSIGNED_BYTE 0x1401
|
||||
#define GL_UNSIGNED_INT 0x1405
|
||||
#define GL_FLOAT 0x1406
|
||||
#define GL_DEPTH_COMPONENT 0x1902
|
||||
#define GL_RED 0x1903
|
||||
#define GL_RGBA 0x1908
|
||||
#define GL_BGRA 0x80E1
|
||||
#define GL_RGBA8 0x8058
|
||||
#define GL_RGBA8UI 0x8D7C
|
||||
#define GL_TEXTURE_BUFFER 0x8C2A
|
||||
#define GL_UNSIGNED_SHORT 0x1403
|
||||
#define GL_RGBA32I 0x8D82
|
||||
#define GL_RGBA_INTEGER 0x8D99
|
||||
#define GL_DEPTH_COMPONENT32F 0x8CAC
|
||||
#define GL_VENDOR 0x1F00
|
||||
#define GL_RENDERER 0x1F01
|
||||
#define GL_VERSION 0x1F02
|
||||
#define GL_NEAREST 0x2600
|
||||
#define GL_LINEAR 0x2601
|
||||
#define GL_TEXTURE_MAG_FILTER 0x2800
|
||||
#define GL_TEXTURE_MIN_FILTER 0x2801
|
||||
#define GL_TEXTURE_WRAP_S 0x2802
|
||||
#define GL_TEXTURE_WRAP_T 0x2803
|
||||
#define GL_CLAMP_TO_EDGE 0x812F
|
||||
#define GL_TEXTURE_MAX_LEVEL 0x813D
|
||||
#define GL_COLOR_BUFFER_BIT 0x00004000
|
||||
#define GL_DEPTH_BUFFER_BIT 0x00000100
|
||||
#define GL_TEXTURE0 0x84C0
|
||||
#define GL_ARRAY_BUFFER 0x8892
|
||||
#define GL_STREAM_DRAW 0x88E0
|
||||
#define GL_DYNAMIC_DRAW 0x88E8
|
||||
#define GL_FRAGMENT_SHADER 0x8B30
|
||||
#define GL_VERTEX_SHADER 0x8B31
|
||||
#define GL_COMPILE_STATUS 0x8B81
|
||||
#define GL_LINK_STATUS 0x8B82
|
||||
#define GL_INFO_LOG_LENGTH 0x8B84
|
||||
#define GL_FRAMEBUFFER 0x8D40
|
||||
#define GL_READ_FRAMEBUFFER 0x8CA8
|
||||
#define GL_DRAW_FRAMEBUFFER 0x8CA9
|
||||
#define GL_COLOR_ATTACHMENT0 0x8CE0
|
||||
#define GL_DEPTH_ATTACHMENT 0x8D00
|
||||
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
|
||||
#define GL_MAP_WRITE_BIT 0x0002
|
||||
#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008
|
||||
#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020
|
||||
#define GL_FIRST_VERTEX_CONVENTION 0x8E4D
|
||||
#define GL_LAST_VERTEX_CONVENTION 0x8E4E
|
||||
#define GL_BACK 0x0405
|
||||
#define GL_PROGRAM_POINT_SIZE 0x8642
|
||||
|
||||
/* name, return type, arguments */
|
||||
#define GSGL_FUNCS(X) \
|
||||
X(glGetError, GLenum, (void)) \
|
||||
X(glGetString, const GLubyte *, (GLenum)) \
|
||||
X(glGetIntegerv, void, (GLenum, GLint *)) \
|
||||
X(glEnable, void, (GLenum)) \
|
||||
X(glDisable, void, (GLenum)) \
|
||||
X(glViewport, void, (GLint, GLint, GLsizei, GLsizei)) \
|
||||
X(glScissor, void, (GLint, GLint, GLsizei, GLsizei)) \
|
||||
X(glClearColor, void, (GLfloat, GLfloat, GLfloat, GLfloat)) \
|
||||
X(glClear, void, (GLbitfield)) \
|
||||
X(glColorMask, void, (GLboolean, GLboolean, GLboolean, GLboolean)) \
|
||||
X(glDepthMask, void, (GLboolean)) \
|
||||
X(glDepthFunc, void, (GLenum)) \
|
||||
X(glBlendFuncSeparate, void, (GLenum, GLenum, GLenum, GLenum)) \
|
||||
X(glBlendEquationSeparate, void, (GLenum, GLenum)) \
|
||||
X(glBlendColor, void, (GLfloat, GLfloat, GLfloat, GLfloat)) \
|
||||
X(glPixelStorei, void, (GLenum, GLint)) \
|
||||
X(glReadPixels, void, (GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, void *)) \
|
||||
X(glGenTextures, void, (GLsizei, GLuint *)) \
|
||||
X(glDeleteTextures, void, (GLsizei, const GLuint *)) \
|
||||
X(glBindTexture, void, (GLenum, GLuint)) \
|
||||
X(glActiveTexture, void, (GLenum)) \
|
||||
X(glTexImage2D, void, (GLenum, GLint, GLint, GLsizei, GLsizei, GLint, GLenum, GLenum, const void *)) \
|
||||
X(glTexSubImage2D, void, (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const void *)) \
|
||||
X(glTexParameteri, void, (GLenum, GLenum, GLint)) \
|
||||
X(glDrawArrays, void, (GLenum, GLint, GLsizei)) \
|
||||
X(glFlush, void, (void)) \
|
||||
X(glFinish, void, (void)) \
|
||||
X(glGenBuffers, void, (GLsizei, GLuint *)) \
|
||||
X(glBindBuffer, void, (GLenum, GLuint)) \
|
||||
X(glBufferData, void, (GLenum, GLsizeiptr, const void *, GLenum)) \
|
||||
X(glBufferSubData, void, (GLenum, GLintptr, GLsizeiptr, const void *)) \
|
||||
X(glTexBuffer, void, (GLenum, GLenum, GLuint)) \
|
||||
X(glMapBufferRange, void *, (GLenum, GLintptr, GLsizeiptr, GLbitfield)) \
|
||||
X(glUnmapBuffer, GLboolean, (GLenum)) \
|
||||
X(glGenVertexArrays, void, (GLsizei, GLuint *)) \
|
||||
X(glBindVertexArray, void, (GLuint)) \
|
||||
X(glEnableVertexAttribArray, void, (GLuint)) \
|
||||
X(glVertexAttribPointer, void, (GLuint, GLint, GLenum, GLboolean, GLsizei, const void *)) \
|
||||
X(glVertexAttribIPointer, void, (GLuint, GLint, GLenum, GLsizei, const void *)) \
|
||||
X(glCreateShader, GLuint, (GLenum)) \
|
||||
X(glShaderSource, void, (GLuint, GLsizei, const GLchar *const *, const GLint *)) \
|
||||
X(glCompileShader, void, (GLuint)) \
|
||||
X(glGetShaderiv, void, (GLuint, GLenum, GLint *)) \
|
||||
X(glGetShaderInfoLog, void, (GLuint, GLsizei, GLsizei *, GLchar *)) \
|
||||
X(glCreateProgram, GLuint, (void)) \
|
||||
X(glAttachShader, void, (GLuint, GLuint)) \
|
||||
X(glBindAttribLocation, void, (GLuint, GLuint, const GLchar *)) \
|
||||
X(glBindFragDataLocationIndexed, void, (GLuint, GLuint, GLuint, const GLchar *)) \
|
||||
X(glLinkProgram, void, (GLuint)) \
|
||||
X(glGetProgramiv, void, (GLuint, GLenum, GLint *)) \
|
||||
X(glGetProgramInfoLog, void, (GLuint, GLsizei, GLsizei *, GLchar *)) \
|
||||
X(glUseProgram, void, (GLuint)) \
|
||||
X(glGetUniformLocation, GLint, (GLuint, const GLchar *)) \
|
||||
X(glUniform1i, void, (GLint, GLint)) \
|
||||
X(glUniform4f, void, (GLint, GLfloat, GLfloat, GLfloat, GLfloat)) \
|
||||
X(glUniform2f, void, (GLint, GLfloat, GLfloat)) \
|
||||
X(glUniform2i, void, (GLint, GLint, GLint)) \
|
||||
X(glUniform4iv, void, (GLint, GLsizei, const GLint *)) \
|
||||
X(glUniform1iv, void, (GLint, GLsizei, const GLint *)) \
|
||||
X(glGenFramebuffers, void, (GLsizei, GLuint *)) \
|
||||
X(glDeleteFramebuffers, void, (GLsizei, const GLuint *)) \
|
||||
X(glBindFramebuffer, void, (GLenum, GLuint)) \
|
||||
X(glFramebufferTexture2D, void, (GLenum, GLenum, GLenum, GLuint, GLint)) \
|
||||
X(glCheckFramebufferStatus, GLenum, (GLenum)) \
|
||||
X(glBlitFramebuffer, void, (GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLbitfield, GLenum)) \
|
||||
X(glProvokingVertex, void, (GLenum)) \
|
||||
X(glDrawBuffer, void, (GLenum)) \
|
||||
X(glReadBuffer, void, (GLenum))
|
||||
|
||||
#define GSGL_DECL(name, ret, args) typedef ret (GLAPIENTRY *PFN_##name) args;
|
||||
GSGL_FUNCS(GSGL_DECL)
|
||||
#undef GSGL_DECL
|
||||
|
||||
/* the platform's loader: returns 0 for a missing entry point */
|
||||
void *plat_gl_proc(const char *name);
|
||||
#endif
|
||||
@@ -0,0 +1,647 @@
|
||||
/* 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) */
|
||||
enum { GLT_glGetError, GLT_glGetString, GLT_glGetIntegerv, GLT_glEnable, GLT_glDisable, GLT_glViewport, GLT_glScissor, GLT_glClearColor, GLT_glClear, GLT_glColorMask, GLT_glDepthMask, GLT_glDepthFunc, GLT_glBlendFuncSeparate, GLT_glBlendEquationSeparate, GLT_glBlendColor, GLT_glPixelStorei, GLT_glReadPixels, GLT_glGenTextures, GLT_glDeleteTextures, GLT_glBindTexture, GLT_glActiveTexture, GLT_glTexImage2D, GLT_glTexSubImage2D, GLT_glTexParameteri, GLT_glDrawArrays, GLT_glFlush, GLT_glFinish, GLT_glGenBuffers, GLT_glBindBuffer, GLT_glBufferData, GLT_glBufferSubData, GLT_glTexBuffer, GLT_glMapBufferRange, GLT_glUnmapBuffer, GLT_glGenVertexArrays, GLT_glBindVertexArray, GLT_glEnableVertexAttribArray, GLT_glVertexAttribPointer, GLT_glVertexAttribIPointer, GLT_glCreateShader, GLT_glShaderSource, GLT_glCompileShader, GLT_glGetShaderiv, GLT_glGetShaderInfoLog, GLT_glCreateProgram, GLT_glAttachShader, GLT_glBindAttribLocation, GLT_glBindFragDataLocationIndexed, GLT_glLinkProgram, GLT_glGetProgramiv, GLT_glGetProgramInfoLog, GLT_glUseProgram, GLT_glGetUniformLocation, GLT_glUniform1i, GLT_glUniform4f, GLT_glUniform2f, GLT_glUniform2i, GLT_glUniform4iv, GLT_glUniform1iv, GLT_glGenFramebuffers, GLT_glDeleteFramebuffers, GLT_glBindFramebuffer, GLT_glFramebufferTexture2D, GLT_glCheckFramebufferStatus, GLT_glBlitFramebuffer, GLT_glProvokingVertex, GLT_glDrawBuffer, GLT_glReadBuffer, GLT_SWAP, GLT_VB, GLT_CALLBACK, GLT_N };
|
||||
#define GSGL_DEF(name, ret, args) PFN_##name r_##name;
|
||||
GSGL_FUNCS(GSGL_DEF)
|
||||
#undef GSGL_DEF
|
||||
static GLenum p_glGetError(void)
|
||||
{
|
||||
if (!glt_q) return r_glGetError();
|
||||
u64 a[0 + 1]; u64 r = 0;
|
||||
glt_call(GLT_glGetError, a, 0, &r);
|
||||
return (GLenum)r;
|
||||
}
|
||||
static const GLubyte * p_glGetString(GLenum a0)
|
||||
{
|
||||
if (!glt_q) return r_glGetString(a0);
|
||||
u64 a[1 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
glt_call(GLT_glGetString, a, 1, &r);
|
||||
return (const GLubyte *)(uintptr_t)r;
|
||||
}
|
||||
static void p_glGetIntegerv(GLenum a0, GLint * a1)
|
||||
{
|
||||
if (!glt_q) { r_glGetIntegerv(a0, a1); return; }
|
||||
u64 a[2 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
a[1] = (u64)(uintptr_t)(a1);
|
||||
glt_call(GLT_glGetIntegerv, a, 2, &r);
|
||||
}
|
||||
static void p_glEnable(GLenum a0)
|
||||
{
|
||||
if (!glt_q) { r_glEnable(a0); return; }
|
||||
u64 *c = glt_cmd(GLT_glEnable, 1, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
}
|
||||
static void p_glDisable(GLenum a0)
|
||||
{
|
||||
if (!glt_q) { r_glDisable(a0); return; }
|
||||
u64 *c = glt_cmd(GLT_glDisable, 1, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
}
|
||||
static void p_glViewport(GLint a0, GLint a1, GLsizei a2, GLsizei a3)
|
||||
{
|
||||
if (!glt_q) { r_glViewport(a0, a1, a2, a3); return; }
|
||||
u64 *c = glt_cmd(GLT_glViewport, 4, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = (u64)(s64)(a2);
|
||||
c[3] = (u64)(s64)(a3);
|
||||
}
|
||||
static void p_glScissor(GLint a0, GLint a1, GLsizei a2, GLsizei a3)
|
||||
{
|
||||
if (!glt_q) { r_glScissor(a0, a1, a2, a3); return; }
|
||||
u64 *c = glt_cmd(GLT_glScissor, 4, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = (u64)(s64)(a2);
|
||||
c[3] = (u64)(s64)(a3);
|
||||
}
|
||||
static void p_glClearColor(GLfloat a0, GLfloat a1, GLfloat a2, GLfloat a3)
|
||||
{
|
||||
if (!glt_q) { r_glClearColor(a0, a1, a2, a3); return; }
|
||||
u64 *c = glt_cmd(GLT_glClearColor, 4, 0);
|
||||
c[0] = glt_fbits(a0);
|
||||
c[1] = glt_fbits(a1);
|
||||
c[2] = glt_fbits(a2);
|
||||
c[3] = glt_fbits(a3);
|
||||
}
|
||||
static void p_glClear(GLbitfield a0)
|
||||
{
|
||||
if (!glt_q) { r_glClear(a0); return; }
|
||||
u64 *c = glt_cmd(GLT_glClear, 1, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
}
|
||||
static void p_glColorMask(GLboolean a0, GLboolean a1, GLboolean a2, GLboolean a3)
|
||||
{
|
||||
if (!glt_q) { r_glColorMask(a0, a1, a2, a3); return; }
|
||||
u64 *c = glt_cmd(GLT_glColorMask, 4, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = (u64)(s64)(a2);
|
||||
c[3] = (u64)(s64)(a3);
|
||||
}
|
||||
static void p_glDepthMask(GLboolean a0)
|
||||
{
|
||||
if (!glt_q) { r_glDepthMask(a0); return; }
|
||||
u64 *c = glt_cmd(GLT_glDepthMask, 1, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
}
|
||||
static void p_glDepthFunc(GLenum a0)
|
||||
{
|
||||
if (!glt_q) { r_glDepthFunc(a0); return; }
|
||||
u64 *c = glt_cmd(GLT_glDepthFunc, 1, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
}
|
||||
static void p_glBlendFuncSeparate(GLenum a0, GLenum a1, GLenum a2, GLenum a3)
|
||||
{
|
||||
if (!glt_q) { r_glBlendFuncSeparate(a0, a1, a2, a3); return; }
|
||||
u64 *c = glt_cmd(GLT_glBlendFuncSeparate, 4, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = (u64)(s64)(a2);
|
||||
c[3] = (u64)(s64)(a3);
|
||||
}
|
||||
static void p_glBlendEquationSeparate(GLenum a0, GLenum a1)
|
||||
{
|
||||
if (!glt_q) { r_glBlendEquationSeparate(a0, a1); return; }
|
||||
u64 *c = glt_cmd(GLT_glBlendEquationSeparate, 2, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
}
|
||||
static void p_glBlendColor(GLfloat a0, GLfloat a1, GLfloat a2, GLfloat a3)
|
||||
{
|
||||
if (!glt_q) { r_glBlendColor(a0, a1, a2, a3); return; }
|
||||
u64 *c = glt_cmd(GLT_glBlendColor, 4, 0);
|
||||
c[0] = glt_fbits(a0);
|
||||
c[1] = glt_fbits(a1);
|
||||
c[2] = glt_fbits(a2);
|
||||
c[3] = glt_fbits(a3);
|
||||
}
|
||||
static void p_glPixelStorei(GLenum a0, GLint a1)
|
||||
{
|
||||
glt_track_store(a0, a1); /* the unpack layout the queued texel copies are sized by */
|
||||
if (!glt_q) { r_glPixelStorei(a0, a1); return; }
|
||||
u64 *c = glt_cmd(GLT_glPixelStorei, 2, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
}
|
||||
static void p_glReadPixels(GLint a0, GLint a1, GLsizei a2, GLsizei a3, GLenum a4, GLenum a5, void * a6)
|
||||
{
|
||||
if (!glt_q) { r_glReadPixels(a0, a1, a2, a3, a4, a5, a6); return; }
|
||||
u64 a[7 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
a[1] = (u64)(s64)(a1);
|
||||
a[2] = (u64)(s64)(a2);
|
||||
a[3] = (u64)(s64)(a3);
|
||||
a[4] = (u64)(s64)(a4);
|
||||
a[5] = (u64)(s64)(a5);
|
||||
a[6] = (u64)(uintptr_t)(a6);
|
||||
glt_call(GLT_glReadPixels, a, 7, &r);
|
||||
}
|
||||
static void p_glGenTextures(GLsizei a0, GLuint * a1)
|
||||
{
|
||||
if (!glt_q) { r_glGenTextures(a0, a1); return; }
|
||||
u64 a[2 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
a[1] = (u64)(uintptr_t)(a1);
|
||||
glt_call(GLT_glGenTextures, a, 2, &r);
|
||||
}
|
||||
static void p_glDeleteTextures(GLsizei a0, const GLuint * a1)
|
||||
{
|
||||
if (!glt_q) { r_glDeleteTextures(a0, a1); return; }
|
||||
size_t nb = a1 ? (size_t)a0 * 4 : 0;
|
||||
u64 *c = glt_cmd(GLT_glDeleteTextures, 2, nb);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = 0; if (nb) memcpy(c + 2, a1, nb);
|
||||
}
|
||||
static void p_glBindTexture(GLenum a0, GLuint a1)
|
||||
{
|
||||
if (!glt_q) { r_glBindTexture(a0, a1); return; }
|
||||
u64 *c = glt_cmd(GLT_glBindTexture, 2, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
}
|
||||
static void p_glActiveTexture(GLenum a0)
|
||||
{
|
||||
if (!glt_q) { r_glActiveTexture(a0); return; }
|
||||
u64 *c = glt_cmd(GLT_glActiveTexture, 1, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
}
|
||||
static void p_glTexImage2D(GLenum a0, GLint a1, GLint a2, GLsizei a3, GLsizei a4, GLint a5, GLenum a6, GLenum a7, const void * a8)
|
||||
{
|
||||
if (!glt_q) { r_glTexImage2D(a0, a1, a2, a3, a4, a5, a6, a7, a8); return; }
|
||||
size_t nb = a8 ? glt_pix_bytes(a3, a4, a6, a7) : 0;
|
||||
u64 *c = glt_cmd(GLT_glTexImage2D, 9, nb);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = (u64)(s64)(a2);
|
||||
c[3] = (u64)(s64)(a3);
|
||||
c[4] = (u64)(s64)(a4);
|
||||
c[5] = (u64)(s64)(a5);
|
||||
c[6] = (u64)(s64)(a6);
|
||||
c[7] = (u64)(s64)(a7);
|
||||
c[8] = 0; if (nb) memcpy(c + 9, a8, nb);
|
||||
}
|
||||
static void p_glTexSubImage2D(GLenum a0, GLint a1, GLint a2, GLint a3, GLsizei a4, GLsizei a5, GLenum a6, GLenum a7, const void * a8)
|
||||
{
|
||||
if (!glt_q) { r_glTexSubImage2D(a0, a1, a2, a3, a4, a5, a6, a7, a8); return; }
|
||||
size_t nb = a8 ? glt_pix_bytes(a4, a5, a6, a7) : 0;
|
||||
u64 *c = glt_cmd(GLT_glTexSubImage2D, 9, nb);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = (u64)(s64)(a2);
|
||||
c[3] = (u64)(s64)(a3);
|
||||
c[4] = (u64)(s64)(a4);
|
||||
c[5] = (u64)(s64)(a5);
|
||||
c[6] = (u64)(s64)(a6);
|
||||
c[7] = (u64)(s64)(a7);
|
||||
c[8] = 0; if (nb) memcpy(c + 9, a8, nb);
|
||||
}
|
||||
static void p_glTexParameteri(GLenum a0, GLenum a1, GLint a2)
|
||||
{
|
||||
if (!glt_q) { r_glTexParameteri(a0, a1, a2); return; }
|
||||
u64 *c = glt_cmd(GLT_glTexParameteri, 3, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = (u64)(s64)(a2);
|
||||
}
|
||||
static void p_glDrawArrays(GLenum a0, GLint a1, GLsizei a2)
|
||||
{
|
||||
if (!glt_q) { r_glDrawArrays(a0, a1, a2); return; }
|
||||
u64 *c = glt_cmd(GLT_glDrawArrays, 3, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = (u64)(s64)(a2);
|
||||
}
|
||||
static void p_glFlush(void)
|
||||
{
|
||||
if (!glt_q) { r_glFlush(); return; }
|
||||
u64 *c = glt_cmd(GLT_glFlush, 0, 0);
|
||||
}
|
||||
static void p_glFinish(void)
|
||||
{
|
||||
if (!glt_q) { r_glFinish(); return; }
|
||||
u64 *c = glt_cmd(GLT_glFinish, 0, 0);
|
||||
}
|
||||
static void p_glGenBuffers(GLsizei a0, GLuint * a1)
|
||||
{
|
||||
if (!glt_q) { r_glGenBuffers(a0, a1); return; }
|
||||
u64 a[2 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
a[1] = (u64)(uintptr_t)(a1);
|
||||
glt_call(GLT_glGenBuffers, a, 2, &r);
|
||||
}
|
||||
static void p_glBindBuffer(GLenum a0, GLuint a1)
|
||||
{
|
||||
if (!glt_q) { r_glBindBuffer(a0, a1); return; }
|
||||
u64 *c = glt_cmd(GLT_glBindBuffer, 2, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
}
|
||||
static void p_glBufferData(GLenum a0, GLsizeiptr a1, const void * a2, GLenum a3)
|
||||
{
|
||||
if (!glt_q) { r_glBufferData(a0, a1, a2, a3); return; }
|
||||
size_t nb = a2 ? (size_t)a1 : 0;
|
||||
u64 *c = glt_cmd(GLT_glBufferData, 4, nb);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = 0; if (nb) memcpy(c + 4, a2, nb);
|
||||
c[3] = (u64)(s64)(a3);
|
||||
}
|
||||
static void p_glBufferSubData(GLenum a0, GLintptr a1, GLsizeiptr a2, const void * a3)
|
||||
{
|
||||
if (!glt_q) { r_glBufferSubData(a0, a1, a2, a3); return; }
|
||||
size_t nb = a3 ? (size_t)a2 : 0;
|
||||
u64 *c = glt_cmd(GLT_glBufferSubData, 4, nb);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = (u64)(s64)(a2);
|
||||
c[3] = 0; if (nb) memcpy(c + 4, a3, nb);
|
||||
}
|
||||
static void p_glTexBuffer(GLenum a0, GLenum a1, GLuint a2)
|
||||
{
|
||||
if (!glt_q) { r_glTexBuffer(a0, a1, a2); return; }
|
||||
u64 *c = glt_cmd(GLT_glTexBuffer, 3, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = (u64)(s64)(a2);
|
||||
}
|
||||
static void * p_glMapBufferRange(GLenum a0, GLintptr a1, GLsizeiptr a2, GLbitfield a3)
|
||||
{
|
||||
if (!glt_q) return r_glMapBufferRange(a0, a1, a2, a3);
|
||||
u64 a[4 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
a[1] = (u64)(s64)(a1);
|
||||
a[2] = (u64)(s64)(a2);
|
||||
a[3] = (u64)(s64)(a3);
|
||||
glt_call(GLT_glMapBufferRange, a, 4, &r);
|
||||
return (void *)(uintptr_t)r;
|
||||
}
|
||||
static GLboolean p_glUnmapBuffer(GLenum a0)
|
||||
{
|
||||
if (!glt_q) return r_glUnmapBuffer(a0);
|
||||
u64 a[1 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
glt_call(GLT_glUnmapBuffer, a, 1, &r);
|
||||
return (GLboolean)r;
|
||||
}
|
||||
static void p_glGenVertexArrays(GLsizei a0, GLuint * a1)
|
||||
{
|
||||
if (!glt_q) { r_glGenVertexArrays(a0, a1); return; }
|
||||
u64 a[2 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
a[1] = (u64)(uintptr_t)(a1);
|
||||
glt_call(GLT_glGenVertexArrays, a, 2, &r);
|
||||
}
|
||||
static void p_glBindVertexArray(GLuint a0)
|
||||
{
|
||||
if (!glt_q) { r_glBindVertexArray(a0); return; }
|
||||
u64 *c = glt_cmd(GLT_glBindVertexArray, 1, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
}
|
||||
static void p_glEnableVertexAttribArray(GLuint a0)
|
||||
{
|
||||
if (!glt_q) { r_glEnableVertexAttribArray(a0); return; }
|
||||
u64 *c = glt_cmd(GLT_glEnableVertexAttribArray, 1, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
}
|
||||
static void p_glVertexAttribPointer(GLuint a0, GLint a1, GLenum a2, GLboolean a3, GLsizei a4, const void * a5)
|
||||
{
|
||||
if (!glt_q) { r_glVertexAttribPointer(a0, a1, a2, a3, a4, a5); return; }
|
||||
u64 *c = glt_cmd(GLT_glVertexAttribPointer, 6, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = (u64)(s64)(a2);
|
||||
c[3] = (u64)(s64)(a3);
|
||||
c[4] = (u64)(s64)(a4);
|
||||
c[5] = (u64)(uintptr_t)(a5);
|
||||
}
|
||||
static void p_glVertexAttribIPointer(GLuint a0, GLint a1, GLenum a2, GLsizei a3, const void * a4)
|
||||
{
|
||||
if (!glt_q) { r_glVertexAttribIPointer(a0, a1, a2, a3, a4); return; }
|
||||
u64 *c = glt_cmd(GLT_glVertexAttribIPointer, 5, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = (u64)(s64)(a2);
|
||||
c[3] = (u64)(s64)(a3);
|
||||
c[4] = (u64)(uintptr_t)(a4);
|
||||
}
|
||||
static GLuint p_glCreateShader(GLenum a0)
|
||||
{
|
||||
if (!glt_q) return r_glCreateShader(a0);
|
||||
u64 a[1 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
glt_call(GLT_glCreateShader, a, 1, &r);
|
||||
return (GLuint)r;
|
||||
}
|
||||
static void p_glShaderSource(GLuint a0, GLsizei a1, const GLchar *const * a2, const GLint * a3)
|
||||
{
|
||||
if (!glt_q) { r_glShaderSource(a0, a1, a2, a3); return; }
|
||||
u64 a[4 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
a[1] = (u64)(s64)(a1);
|
||||
a[2] = (u64)(uintptr_t)(a2);
|
||||
a[3] = (u64)(uintptr_t)(a3);
|
||||
glt_call(GLT_glShaderSource, a, 4, &r);
|
||||
}
|
||||
static void p_glCompileShader(GLuint a0)
|
||||
{
|
||||
if (!glt_q) { r_glCompileShader(a0); return; }
|
||||
u64 *c = glt_cmd(GLT_glCompileShader, 1, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
}
|
||||
static void p_glGetShaderiv(GLuint a0, GLenum a1, GLint * a2)
|
||||
{
|
||||
if (!glt_q) { r_glGetShaderiv(a0, a1, a2); return; }
|
||||
u64 a[3 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
a[1] = (u64)(s64)(a1);
|
||||
a[2] = (u64)(uintptr_t)(a2);
|
||||
glt_call(GLT_glGetShaderiv, a, 3, &r);
|
||||
}
|
||||
static void p_glGetShaderInfoLog(GLuint a0, GLsizei a1, GLsizei * a2, GLchar * a3)
|
||||
{
|
||||
if (!glt_q) { r_glGetShaderInfoLog(a0, a1, a2, a3); return; }
|
||||
u64 a[4 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
a[1] = (u64)(s64)(a1);
|
||||
a[2] = (u64)(uintptr_t)(a2);
|
||||
a[3] = (u64)(uintptr_t)(a3);
|
||||
glt_call(GLT_glGetShaderInfoLog, a, 4, &r);
|
||||
}
|
||||
static GLuint p_glCreateProgram(void)
|
||||
{
|
||||
if (!glt_q) return r_glCreateProgram();
|
||||
u64 a[0 + 1]; u64 r = 0;
|
||||
glt_call(GLT_glCreateProgram, a, 0, &r);
|
||||
return (GLuint)r;
|
||||
}
|
||||
static void p_glAttachShader(GLuint a0, GLuint a1)
|
||||
{
|
||||
if (!glt_q) { r_glAttachShader(a0, a1); return; }
|
||||
u64 *c = glt_cmd(GLT_glAttachShader, 2, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
}
|
||||
static void p_glBindAttribLocation(GLuint a0, GLuint a1, const GLchar * a2)
|
||||
{
|
||||
if (!glt_q) { r_glBindAttribLocation(a0, a1, a2); return; }
|
||||
u64 a[3 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
a[1] = (u64)(s64)(a1);
|
||||
a[2] = (u64)(uintptr_t)(a2);
|
||||
glt_call(GLT_glBindAttribLocation, a, 3, &r);
|
||||
}
|
||||
static void p_glBindFragDataLocationIndexed(GLuint a0, GLuint a1, GLuint a2, const GLchar * a3)
|
||||
{
|
||||
if (!glt_q) { r_glBindFragDataLocationIndexed(a0, a1, a2, a3); return; }
|
||||
u64 a[4 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
a[1] = (u64)(s64)(a1);
|
||||
a[2] = (u64)(s64)(a2);
|
||||
a[3] = (u64)(uintptr_t)(a3);
|
||||
glt_call(GLT_glBindFragDataLocationIndexed, a, 4, &r);
|
||||
}
|
||||
static void p_glLinkProgram(GLuint a0)
|
||||
{
|
||||
if (!glt_q) { r_glLinkProgram(a0); return; }
|
||||
u64 *c = glt_cmd(GLT_glLinkProgram, 1, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
}
|
||||
static void p_glGetProgramiv(GLuint a0, GLenum a1, GLint * a2)
|
||||
{
|
||||
if (!glt_q) { r_glGetProgramiv(a0, a1, a2); return; }
|
||||
u64 a[3 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
a[1] = (u64)(s64)(a1);
|
||||
a[2] = (u64)(uintptr_t)(a2);
|
||||
glt_call(GLT_glGetProgramiv, a, 3, &r);
|
||||
}
|
||||
static void p_glGetProgramInfoLog(GLuint a0, GLsizei a1, GLsizei * a2, GLchar * a3)
|
||||
{
|
||||
if (!glt_q) { r_glGetProgramInfoLog(a0, a1, a2, a3); return; }
|
||||
u64 a[4 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
a[1] = (u64)(s64)(a1);
|
||||
a[2] = (u64)(uintptr_t)(a2);
|
||||
a[3] = (u64)(uintptr_t)(a3);
|
||||
glt_call(GLT_glGetProgramInfoLog, a, 4, &r);
|
||||
}
|
||||
static void p_glUseProgram(GLuint a0)
|
||||
{
|
||||
if (!glt_q) { r_glUseProgram(a0); return; }
|
||||
u64 *c = glt_cmd(GLT_glUseProgram, 1, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
}
|
||||
static GLint p_glGetUniformLocation(GLuint a0, const GLchar * a1)
|
||||
{
|
||||
if (!glt_q) return r_glGetUniformLocation(a0, a1);
|
||||
u64 a[2 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
a[1] = (u64)(uintptr_t)(a1);
|
||||
glt_call(GLT_glGetUniformLocation, a, 2, &r);
|
||||
return (GLint)r;
|
||||
}
|
||||
static void p_glUniform1i(GLint a0, GLint a1)
|
||||
{
|
||||
if (!glt_q) { r_glUniform1i(a0, a1); return; }
|
||||
u64 *c = glt_cmd(GLT_glUniform1i, 2, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
}
|
||||
static void p_glUniform4f(GLint a0, GLfloat a1, GLfloat a2, GLfloat a3, GLfloat a4)
|
||||
{
|
||||
if (!glt_q) { r_glUniform4f(a0, a1, a2, a3, a4); return; }
|
||||
u64 *c = glt_cmd(GLT_glUniform4f, 5, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = glt_fbits(a1);
|
||||
c[2] = glt_fbits(a2);
|
||||
c[3] = glt_fbits(a3);
|
||||
c[4] = glt_fbits(a4);
|
||||
}
|
||||
static void p_glUniform2f(GLint a0, GLfloat a1, GLfloat a2)
|
||||
{
|
||||
if (!glt_q) { r_glUniform2f(a0, a1, a2); return; }
|
||||
u64 *c = glt_cmd(GLT_glUniform2f, 3, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = glt_fbits(a1);
|
||||
c[2] = glt_fbits(a2);
|
||||
}
|
||||
static void p_glUniform2i(GLint a0, GLint a1, GLint a2)
|
||||
{
|
||||
if (!glt_q) { r_glUniform2i(a0, a1, a2); return; }
|
||||
u64 *c = glt_cmd(GLT_glUniform2i, 3, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = (u64)(s64)(a2);
|
||||
}
|
||||
static void p_glUniform4iv(GLint a0, GLsizei a1, const GLint * a2)
|
||||
{
|
||||
if (!glt_q) { r_glUniform4iv(a0, a1, a2); return; }
|
||||
size_t nb = a2 ? (size_t)a1 * 16 : 0;
|
||||
u64 *c = glt_cmd(GLT_glUniform4iv, 3, nb);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = 0; if (nb) memcpy(c + 3, a2, nb);
|
||||
}
|
||||
static void p_glUniform1iv(GLint a0, GLsizei a1, const GLint * a2)
|
||||
{
|
||||
if (!glt_q) { r_glUniform1iv(a0, a1, a2); return; }
|
||||
size_t nb = a2 ? (size_t)a1 * 4 : 0;
|
||||
u64 *c = glt_cmd(GLT_glUniform1iv, 3, nb);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = 0; if (nb) memcpy(c + 3, a2, nb);
|
||||
}
|
||||
static void p_glGenFramebuffers(GLsizei a0, GLuint * a1)
|
||||
{
|
||||
if (!glt_q) { r_glGenFramebuffers(a0, a1); return; }
|
||||
u64 a[2 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
a[1] = (u64)(uintptr_t)(a1);
|
||||
glt_call(GLT_glGenFramebuffers, a, 2, &r);
|
||||
}
|
||||
static void p_glDeleteFramebuffers(GLsizei a0, const GLuint * a1)
|
||||
{
|
||||
if (!glt_q) { r_glDeleteFramebuffers(a0, a1); return; }
|
||||
size_t nb = a1 ? (size_t)a0 * 4 : 0;
|
||||
u64 *c = glt_cmd(GLT_glDeleteFramebuffers, 2, nb);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = 0; if (nb) memcpy(c + 2, a1, nb);
|
||||
}
|
||||
static void p_glBindFramebuffer(GLenum a0, GLuint a1)
|
||||
{
|
||||
if (!glt_q) { r_glBindFramebuffer(a0, a1); return; }
|
||||
u64 *c = glt_cmd(GLT_glBindFramebuffer, 2, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
}
|
||||
static void p_glFramebufferTexture2D(GLenum a0, GLenum a1, GLenum a2, GLuint a3, GLint a4)
|
||||
{
|
||||
if (!glt_q) { r_glFramebufferTexture2D(a0, a1, a2, a3, a4); return; }
|
||||
u64 *c = glt_cmd(GLT_glFramebufferTexture2D, 5, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = (u64)(s64)(a2);
|
||||
c[3] = (u64)(s64)(a3);
|
||||
c[4] = (u64)(s64)(a4);
|
||||
}
|
||||
static GLenum p_glCheckFramebufferStatus(GLenum a0)
|
||||
{
|
||||
if (!glt_q) return r_glCheckFramebufferStatus(a0);
|
||||
u64 a[1 + 1]; u64 r = 0;
|
||||
a[0] = (u64)(s64)(a0);
|
||||
glt_call(GLT_glCheckFramebufferStatus, a, 1, &r);
|
||||
return (GLenum)r;
|
||||
}
|
||||
static void p_glBlitFramebuffer(GLint a0, GLint a1, GLint a2, GLint a3, GLint a4, GLint a5, GLint a6, GLint a7, GLbitfield a8, GLenum a9)
|
||||
{
|
||||
if (!glt_q) { r_glBlitFramebuffer(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); return; }
|
||||
u64 *c = glt_cmd(GLT_glBlitFramebuffer, 10, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
c[1] = (u64)(s64)(a1);
|
||||
c[2] = (u64)(s64)(a2);
|
||||
c[3] = (u64)(s64)(a3);
|
||||
c[4] = (u64)(s64)(a4);
|
||||
c[5] = (u64)(s64)(a5);
|
||||
c[6] = (u64)(s64)(a6);
|
||||
c[7] = (u64)(s64)(a7);
|
||||
c[8] = (u64)(s64)(a8);
|
||||
c[9] = (u64)(s64)(a9);
|
||||
}
|
||||
static void p_glProvokingVertex(GLenum a0)
|
||||
{
|
||||
if (!glt_q) { r_glProvokingVertex(a0); return; }
|
||||
u64 *c = glt_cmd(GLT_glProvokingVertex, 1, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
}
|
||||
static void p_glDrawBuffer(GLenum a0)
|
||||
{
|
||||
if (!glt_q) { r_glDrawBuffer(a0); return; }
|
||||
u64 *c = glt_cmd(GLT_glDrawBuffer, 1, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
}
|
||||
static void p_glReadBuffer(GLenum a0)
|
||||
{
|
||||
if (!glt_q) { r_glReadBuffer(a0); return; }
|
||||
u64 *c = glt_cmd(GLT_glReadBuffer, 1, 0);
|
||||
c[0] = (u64)(s64)(a0);
|
||||
}
|
||||
static const unsigned char glt_nargs[GLT_N] = { 0, 1, 2, 1, 1, 4, 4, 4, 1, 4, 1, 1, 4, 2, 4, 2, 7, 2, 2, 2, 1, 9, 9, 3, 3, 0, 0, 2, 2, 4, 4, 3, 4, 1, 2, 1, 1, 6, 5, 1, 4, 1, 3, 4, 0, 2, 3, 4, 1, 3, 4, 1, 2, 2, 5, 3, 3, 3, 3, 2, 2, 2, 5, 1, 10, 1, 1, 1, 0, 1, 0 }; /* ..., SWAP, VB, CALLBACK */
|
||||
/* the GL thread: one recorded call (c: its arguments, then its data; rp: where a result goes) */
|
||||
static void glt_exec(u32 id, const u64 *c, size_t blob_n, u64 *rp)
|
||||
{
|
||||
(void)blob_n; (void)rp;
|
||||
switch (id) {
|
||||
case GLT_glGetError: *rp = (u64)r_glGetError(); break;
|
||||
case GLT_glGetString: *rp = (u64)(uintptr_t)r_glGetString((GLenum)(c[0])); break;
|
||||
case GLT_glGetIntegerv: r_glGetIntegerv((GLenum)(c[0]), (GLint *)(uintptr_t)(c[1])); break;
|
||||
case GLT_glEnable: r_glEnable((GLenum)(c[0])); break;
|
||||
case GLT_glDisable: r_glDisable((GLenum)(c[0])); break;
|
||||
case GLT_glViewport: r_glViewport((GLint)(c[0]), (GLint)(c[1]), (GLsizei)(c[2]), (GLsizei)(c[3])); break;
|
||||
case GLT_glScissor: r_glScissor((GLint)(c[0]), (GLint)(c[1]), (GLsizei)(c[2]), (GLsizei)(c[3])); break;
|
||||
case GLT_glClearColor: r_glClearColor(glt_bitsf(c[0]), glt_bitsf(c[1]), glt_bitsf(c[2]), glt_bitsf(c[3])); break;
|
||||
case GLT_glClear: r_glClear((GLbitfield)(c[0])); break;
|
||||
case GLT_glColorMask: r_glColorMask((GLboolean)(c[0]), (GLboolean)(c[1]), (GLboolean)(c[2]), (GLboolean)(c[3])); break;
|
||||
case GLT_glDepthMask: r_glDepthMask((GLboolean)(c[0])); break;
|
||||
case GLT_glDepthFunc: r_glDepthFunc((GLenum)(c[0])); break;
|
||||
case GLT_glBlendFuncSeparate: r_glBlendFuncSeparate((GLenum)(c[0]), (GLenum)(c[1]), (GLenum)(c[2]), (GLenum)(c[3])); break;
|
||||
case GLT_glBlendEquationSeparate: r_glBlendEquationSeparate((GLenum)(c[0]), (GLenum)(c[1])); break;
|
||||
case GLT_glBlendColor: r_glBlendColor(glt_bitsf(c[0]), glt_bitsf(c[1]), glt_bitsf(c[2]), glt_bitsf(c[3])); break;
|
||||
case GLT_glPixelStorei: r_glPixelStorei((GLenum)(c[0]), (GLint)(c[1])); break;
|
||||
case GLT_glReadPixels: r_glReadPixels((GLint)(c[0]), (GLint)(c[1]), (GLsizei)(c[2]), (GLsizei)(c[3]), (GLenum)(c[4]), (GLenum)(c[5]), (void *)(uintptr_t)(c[6])); break;
|
||||
case GLT_glGenTextures: r_glGenTextures((GLsizei)(c[0]), (GLuint *)(uintptr_t)(c[1])); break;
|
||||
case GLT_glDeleteTextures: r_glDeleteTextures((GLsizei)(c[0]), blob_n ? (const GLuint *)(const void *)(c + 2) : (const GLuint *)0); break;
|
||||
case GLT_glBindTexture: r_glBindTexture((GLenum)(c[0]), (GLuint)(c[1])); break;
|
||||
case GLT_glActiveTexture: r_glActiveTexture((GLenum)(c[0])); break;
|
||||
case GLT_glTexImage2D: r_glTexImage2D((GLenum)(c[0]), (GLint)(c[1]), (GLint)(c[2]), (GLsizei)(c[3]), (GLsizei)(c[4]), (GLint)(c[5]), (GLenum)(c[6]), (GLenum)(c[7]), blob_n ? (const void *)(const void *)(c + 9) : (const void *)0); break;
|
||||
case GLT_glTexSubImage2D: r_glTexSubImage2D((GLenum)(c[0]), (GLint)(c[1]), (GLint)(c[2]), (GLint)(c[3]), (GLsizei)(c[4]), (GLsizei)(c[5]), (GLenum)(c[6]), (GLenum)(c[7]), blob_n ? (const void *)(const void *)(c + 9) : (const void *)0); break;
|
||||
case GLT_glTexParameteri: r_glTexParameteri((GLenum)(c[0]), (GLenum)(c[1]), (GLint)(c[2])); break;
|
||||
case GLT_glDrawArrays: r_glDrawArrays((GLenum)(c[0]), (GLint)(c[1]), (GLsizei)(c[2])); break;
|
||||
case GLT_glFlush: r_glFlush(); break;
|
||||
case GLT_glFinish: r_glFinish(); break;
|
||||
case GLT_glGenBuffers: r_glGenBuffers((GLsizei)(c[0]), (GLuint *)(uintptr_t)(c[1])); break;
|
||||
case GLT_glBindBuffer: r_glBindBuffer((GLenum)(c[0]), (GLuint)(c[1])); break;
|
||||
case GLT_glBufferData: r_glBufferData((GLenum)(c[0]), (GLsizeiptr)(c[1]), blob_n ? (const void *)(const void *)(c + 4) : (const void *)0, (GLenum)(c[3])); break;
|
||||
case GLT_glBufferSubData: r_glBufferSubData((GLenum)(c[0]), (GLintptr)(c[1]), (GLsizeiptr)(c[2]), blob_n ? (const void *)(const void *)(c + 4) : (const void *)0); break;
|
||||
case GLT_glTexBuffer: r_glTexBuffer((GLenum)(c[0]), (GLenum)(c[1]), (GLuint)(c[2])); break;
|
||||
case GLT_glMapBufferRange: *rp = (u64)(uintptr_t)r_glMapBufferRange((GLenum)(c[0]), (GLintptr)(c[1]), (GLsizeiptr)(c[2]), (GLbitfield)(c[3])); break;
|
||||
case GLT_glUnmapBuffer: *rp = (u64)r_glUnmapBuffer((GLenum)(c[0])); break;
|
||||
case GLT_glGenVertexArrays: r_glGenVertexArrays((GLsizei)(c[0]), (GLuint *)(uintptr_t)(c[1])); break;
|
||||
case GLT_glBindVertexArray: r_glBindVertexArray((GLuint)(c[0])); break;
|
||||
case GLT_glEnableVertexAttribArray: r_glEnableVertexAttribArray((GLuint)(c[0])); break;
|
||||
case GLT_glVertexAttribPointer: r_glVertexAttribPointer((GLuint)(c[0]), (GLint)(c[1]), (GLenum)(c[2]), (GLboolean)(c[3]), (GLsizei)(c[4]), (const void *)(uintptr_t)(c[5])); break;
|
||||
case GLT_glVertexAttribIPointer: r_glVertexAttribIPointer((GLuint)(c[0]), (GLint)(c[1]), (GLenum)(c[2]), (GLsizei)(c[3]), (const void *)(uintptr_t)(c[4])); break;
|
||||
case GLT_glCreateShader: *rp = (u64)r_glCreateShader((GLenum)(c[0])); break;
|
||||
case GLT_glShaderSource: r_glShaderSource((GLuint)(c[0]), (GLsizei)(c[1]), (const GLchar *const *)(uintptr_t)(c[2]), (const GLint *)(uintptr_t)(c[3])); break;
|
||||
case GLT_glCompileShader: r_glCompileShader((GLuint)(c[0])); break;
|
||||
case GLT_glGetShaderiv: r_glGetShaderiv((GLuint)(c[0]), (GLenum)(c[1]), (GLint *)(uintptr_t)(c[2])); break;
|
||||
case GLT_glGetShaderInfoLog: r_glGetShaderInfoLog((GLuint)(c[0]), (GLsizei)(c[1]), (GLsizei *)(uintptr_t)(c[2]), (GLchar *)(uintptr_t)(c[3])); break;
|
||||
case GLT_glCreateProgram: *rp = (u64)r_glCreateProgram(); break;
|
||||
case GLT_glAttachShader: r_glAttachShader((GLuint)(c[0]), (GLuint)(c[1])); break;
|
||||
case GLT_glBindAttribLocation: r_glBindAttribLocation((GLuint)(c[0]), (GLuint)(c[1]), (const GLchar *)(uintptr_t)(c[2])); break;
|
||||
case GLT_glBindFragDataLocationIndexed: r_glBindFragDataLocationIndexed((GLuint)(c[0]), (GLuint)(c[1]), (GLuint)(c[2]), (const GLchar *)(uintptr_t)(c[3])); break;
|
||||
case GLT_glLinkProgram: r_glLinkProgram((GLuint)(c[0])); break;
|
||||
case GLT_glGetProgramiv: r_glGetProgramiv((GLuint)(c[0]), (GLenum)(c[1]), (GLint *)(uintptr_t)(c[2])); break;
|
||||
case GLT_glGetProgramInfoLog: r_glGetProgramInfoLog((GLuint)(c[0]), (GLsizei)(c[1]), (GLsizei *)(uintptr_t)(c[2]), (GLchar *)(uintptr_t)(c[3])); break;
|
||||
case GLT_glUseProgram: r_glUseProgram((GLuint)(c[0])); break;
|
||||
case GLT_glGetUniformLocation: *rp = (u64)r_glGetUniformLocation((GLuint)(c[0]), (const GLchar *)(uintptr_t)(c[1])); break;
|
||||
case GLT_glUniform1i: r_glUniform1i((GLint)(c[0]), (GLint)(c[1])); break;
|
||||
case GLT_glUniform4f: r_glUniform4f((GLint)(c[0]), glt_bitsf(c[1]), glt_bitsf(c[2]), glt_bitsf(c[3]), glt_bitsf(c[4])); break;
|
||||
case GLT_glUniform2f: r_glUniform2f((GLint)(c[0]), glt_bitsf(c[1]), glt_bitsf(c[2])); break;
|
||||
case GLT_glUniform2i: r_glUniform2i((GLint)(c[0]), (GLint)(c[1]), (GLint)(c[2])); break;
|
||||
case GLT_glUniform4iv: r_glUniform4iv((GLint)(c[0]), (GLsizei)(c[1]), blob_n ? (const GLint *)(const void *)(c + 3) : (const GLint *)0); break;
|
||||
case GLT_glUniform1iv: r_glUniform1iv((GLint)(c[0]), (GLsizei)(c[1]), blob_n ? (const GLint *)(const void *)(c + 3) : (const GLint *)0); break;
|
||||
case GLT_glGenFramebuffers: r_glGenFramebuffers((GLsizei)(c[0]), (GLuint *)(uintptr_t)(c[1])); break;
|
||||
case GLT_glDeleteFramebuffers: r_glDeleteFramebuffers((GLsizei)(c[0]), blob_n ? (const GLuint *)(const void *)(c + 2) : (const GLuint *)0); break;
|
||||
case GLT_glBindFramebuffer: r_glBindFramebuffer((GLenum)(c[0]), (GLuint)(c[1])); break;
|
||||
case GLT_glFramebufferTexture2D: r_glFramebufferTexture2D((GLenum)(c[0]), (GLenum)(c[1]), (GLenum)(c[2]), (GLuint)(c[3]), (GLint)(c[4])); break;
|
||||
case GLT_glCheckFramebufferStatus: *rp = (u64)r_glCheckFramebufferStatus((GLenum)(c[0])); break;
|
||||
case GLT_glBlitFramebuffer: r_glBlitFramebuffer((GLint)(c[0]), (GLint)(c[1]), (GLint)(c[2]), (GLint)(c[3]), (GLint)(c[4]), (GLint)(c[5]), (GLint)(c[6]), (GLint)(c[7]), (GLbitfield)(c[8]), (GLenum)(c[9])); break;
|
||||
case GLT_glProvokingVertex: r_glProvokingVertex((GLenum)(c[0])); break;
|
||||
case GLT_glDrawBuffer: r_glDrawBuffer((GLenum)(c[0])); break;
|
||||
case GLT_glReadBuffer: r_glReadBuffer((GLenum)(c[0])); break;
|
||||
default: glt_exec_extra(id, c, blob_n); break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/* gs_swizzle.h: where a pixel lives in GS local memory (4 MB) for each pixel
|
||||
* storage format. Memory is pages of 8 KB (32 blocks of 256 bytes); inside a
|
||||
* block, pixels are arranged in 4 columns. The layouts below are the ones the
|
||||
* GS hardware uses (GS User's Manual, section 8). */
|
||||
#ifndef GS_SWIZZLE_H
|
||||
#define GS_SWIZZLE_H
|
||||
#include <stdint.h>
|
||||
|
||||
static const uint8_t gs_block32[4][8] = {
|
||||
{0, 1, 4, 5, 16, 17, 20, 21}, {2, 3, 6, 7, 18, 19, 22, 23},
|
||||
{8, 9, 12, 13, 24, 25, 28, 29}, {10, 11, 14, 15, 26, 27, 30, 31}};
|
||||
static const uint8_t gs_block16[8][4] = {
|
||||
{0, 2, 8, 10}, {1, 3, 9, 11}, {4, 6, 12, 14}, {5, 7, 13, 15},
|
||||
{16, 18, 24, 26}, {17, 19, 25, 27}, {20, 22, 28, 30}, {21, 23, 29, 31}};
|
||||
static const uint8_t gs_block16s[8][4] = {
|
||||
{0, 2, 16, 18}, {1, 3, 17, 19}, {8, 10, 24, 26}, {9, 11, 25, 27},
|
||||
{4, 6, 20, 22}, {5, 7, 21, 23}, {12, 14, 28, 30}, {13, 15, 29, 31}};
|
||||
/* 8-bit uses the 32-bit block order, 4-bit the 16-bit one */
|
||||
#define gs_block8 gs_block32
|
||||
#define gs_block4 gs_block16
|
||||
|
||||
static uint8_t gs_col32[8][8]; /* word in block */
|
||||
static uint8_t gs_col16[8][16]; /* halfword in block */
|
||||
static uint8_t gs_col8[16][16]; /* byte in block */
|
||||
static uint16_t gs_col4[16][32]; /* nibble in block */
|
||||
|
||||
static void gs_swizzle_init(void)
|
||||
{
|
||||
/* 32-bit: a column is 8x2 pixels; pairs of words alternate between rows */
|
||||
for (int y = 0; y < 8; y++)
|
||||
for (int x = 0; x < 8; x++)
|
||||
gs_col32[y][x] = (uint8_t)((y >> 1) * 16 + (x >> 1) * 4 + (y & 1) * 2 + (x & 1));
|
||||
/* 16-bit: two 16-bit pixels share a 32-bit word; x bit 3 picks the half */
|
||||
for (int y = 0; y < 8; y++)
|
||||
for (int x = 0; x < 16; x++)
|
||||
gs_col16[y][x] = (uint8_t)(gs_col32[y][x & 7] * 2 + ((x >> 3) & 1));
|
||||
/* 8-bit and 4-bit: each column (4 rows) is laid out like a 32-bit column
|
||||
* in which odd columns swap their halves */
|
||||
for (int y = 0; y < 16; y++)
|
||||
for (int x = 0; x < 16; x++) {
|
||||
int col = y >> 2, r = y & 3;
|
||||
int half = ((r >> 1) ^ (col & 1)) & 1; /* which half of the column's words */
|
||||
int grp = x >> 3, xi = x & 7;
|
||||
int base[8] = {0, 4, 16, 20, 32, 36, 48, 52};
|
||||
int b = base[(xi + (half ? 4 : 0)) & 7];
|
||||
if (r >> 1) b += 1; /* rows 2 and 3 hold the odd bytes */
|
||||
gs_col8[y][x] = (uint8_t)(col * 64 + b + (r & 1) * 8 + grp * 2);
|
||||
}
|
||||
for (int y = 0; y < 16; y++)
|
||||
for (int x = 0; x < 32; x++) {
|
||||
int col = y >> 2, r = y & 3;
|
||||
int half = ((r >> 1) ^ (col & 1)) & 1;
|
||||
int grp = x >> 3, xi = x & 7;
|
||||
int base[8] = {0, 8, 32, 40, 64, 72, 96, 104};
|
||||
int b = base[(xi + (half ? 4 : 0)) & 7];
|
||||
if (r >> 1) b += 1;
|
||||
gs_col4[y][x] = (uint16_t)(col * 128 + b + (r & 1) * 16 + grp * 2);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,34 @@
|
||||
/* gs_types.h: the software GS's (gs.c) register context, vertex, primitive
|
||||
* and pipeline-state types, shared with the GL renderer (gs_gl.c). */
|
||||
#ifndef GS_TYPES_H
|
||||
#define GS_TYPES_H
|
||||
#include "uya_host.h"
|
||||
|
||||
typedef struct {
|
||||
u64 tex0, tex1, clamp, xyoffset, scissor, alpha, test, fba, frame, zbuf, miptbp1, miptbp2;
|
||||
} gs_ctx_t;
|
||||
|
||||
typedef struct { float x, y; u32 z; u8 r, g, b, a; float s, t, q, u, v; u8 f; } vtx_t;
|
||||
|
||||
/* a texture level as the drawing sees it: w, h: decoded; lw, lh: the level's
|
||||
* size (REPEAT/CLAMP); tci, ser: the texture cache entry and its build (GL) */
|
||||
typedef struct { const u32 *px; int w, h, lw, lh; int tci; u32 ser; } tlevel_t;
|
||||
typedef struct {
|
||||
u8 tme, fge, abe, fst, iip, ate, atst, afail, zte, ztst, zmsk;
|
||||
u8 ba, bb, bc, bd, tfx, tcc, fba, clampc, lcm, mxl, mmag, mmin, l, wms, wmt, dthe_;
|
||||
u8 date, datm; /* destination alpha test: only pixels whose alpha MSB is DATM are drawn */
|
||||
u8 aref, fix, fogr, fogg, fogb;
|
||||
u8 defer; /* GL: levels not decoded yet (their memory is a GPU target's) */
|
||||
u16 rt; /* GL: the texture is a GPU target (index + 1) */
|
||||
u32 fbp, fbw, fpsm, fbmsk, zbp, zpsm;
|
||||
int sx0, sx1, sy0, sy1;
|
||||
int minu, maxu, minv, maxv;
|
||||
float tw, th, k;
|
||||
tlevel_t lv[7];
|
||||
u64 tex0, tex1, miptbp1, miptbp2; /* for the REGION modes' reads outside a level */
|
||||
u64 clamp, texa; /* GL: for decoding a deferred level */
|
||||
} pcmd_t;
|
||||
typedef struct { u32 st; u8 type; vtx_t v[3]; } pprim_t; /* a primitive: its state (index) and vertices */
|
||||
|
||||
#define TC_N 4096 /* texture cache entries */
|
||||
#endif
|
||||
@@ -0,0 +1,93 @@
|
||||
/* RAC1 Wrapper: runs the decompiled Ratchet & Clank (PAL, SCES_509.16) natively.
|
||||
* usage: rac1_wrapper [SCES_509.16 or -] [disc.iso] (the executable is read from the disc by default)
|
||||
* Both come from the user's own disc; nothing from them is in this project. */
|
||||
#include "uya_runtime.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* The disc image: the second argument, RAC1_ISO, or `iso=PATH` in
|
||||
* rac1_wrapper.cfg next to the program. */
|
||||
static const char *cfg_value(const char *key)
|
||||
{
|
||||
static char line[4][1024];
|
||||
static int k;
|
||||
char p[1100];
|
||||
snprintf(p, sizeof p, "%s/rac1_wrapper.cfg", plat_exe_dir());
|
||||
FILE *f = fopen(p, "r");
|
||||
if (!f) return 0;
|
||||
char *b = line[k++ & 3];
|
||||
size_t kl = strlen(key);
|
||||
while (fgets(b, sizeof line[0], f)) {
|
||||
b[strcspn(b, "\r\n")] = 0;
|
||||
if (!strncmp(b, key, kl) && b[kl] == '=') { fclose(f); return b + kl + 1; }
|
||||
}
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *find_iso(int argc, char **argv)
|
||||
{
|
||||
if (argc > 2) return argv[2];
|
||||
if (getenv("RAC1_ISO")) return getenv("RAC1_ISO");
|
||||
const char *v = cfg_value("iso");
|
||||
if (v) return v;
|
||||
uya_log("no disc image: put `iso=C:\\path\\to\\Ratchet & Clank (Europe).iso` in %s/rac1_wrapper.cfg", plat_exe_dir());
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* SCES_509.16: read from the disc image itself (`disc:`), unless the first
|
||||
* argument or `elf=` in the cfg names a file. */
|
||||
static const char *find_elf(int argc, char **argv)
|
||||
{
|
||||
if (argc > 1 && strcmp(argv[1], "-")) return argv[1];
|
||||
const char *v = cfg_value("elf");
|
||||
if (v) return v;
|
||||
return "disc:SCES_509.16";
|
||||
}
|
||||
|
||||
/* Sections of the executable: everything but code. .vutext is VU microcode
|
||||
* (data the game uploads by DMA). */
|
||||
static const char *const exe_sections[] = {
|
||||
".vutext", "core.data", "core.rdata", "core.bss", "core.lit",
|
||||
".lit", ".bss", ".data", "lvl.vtbl", "lvl.camvtbl", "lvl.sndvtbl", 0
|
||||
};
|
||||
|
||||
void rac1_boot(void);
|
||||
void rac1_run(void);
|
||||
|
||||
int uya_main(int argc, char **argv)
|
||||
{
|
||||
setvbuf(stdout, 0, _IONBF, 0);
|
||||
atexit(plat_at_exit);
|
||||
uya_log_open();
|
||||
{ /* renderer=gl|soft in the cfg (UYA_RENDERER overrides it) */
|
||||
extern const char *uya_renderer_cfg;
|
||||
const char *r = cfg_value("renderer");
|
||||
uya_renderer_cfg = r ? strdup(r) : 0;
|
||||
}
|
||||
const char *elf = find_elf(argc, argv);
|
||||
if (plat_map_fixed(PS2_RAM_LO, PS2_RAM_HI - PS2_RAM_LO, 1) ||
|
||||
plat_map_fixed(PS2_SPR, PS2_SPR_SIZE, 0) ||
|
||||
plat_map_fixed(PS2_EE_REGS, PS2_EE_REGS_SZ, 0) ||
|
||||
plat_map_fixed(PS2_VU_MEM, PS2_VU_MEM_SZ, 0) ||
|
||||
plat_map_fixed(PS2_GS_PRIV, PS2_GS_PRIV_SZ, 0)) {
|
||||
uya_log("the PS2 address space could not be mapped");
|
||||
return 1;
|
||||
}
|
||||
plat_install_trap_handler();
|
||||
{
|
||||
const char *iso = find_iso(argc, argv);
|
||||
if (!iso || uya_disc_open(iso) != 0) { uya_log("the disc image could not be opened"); return 1; }
|
||||
uya_log("disc image: %s", iso);
|
||||
}
|
||||
if (uya_load_elf_sections(elf, exe_sections) <= 0) return 1;
|
||||
/* code: every byte traps until a trampoline covers it */
|
||||
uya_fill_code_traps(0x00112380, 0x0012F580); /* core.text */
|
||||
uya_fill_code_traps(0x001E9080, 0x0023E740); /* .text (the game program) */
|
||||
uya_install_trampolines();
|
||||
atexit(uya_report);
|
||||
rac1_boot();
|
||||
rac1_run();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#ifdef __linux__
|
||||
#include "uya_runtime.h"
|
||||
int main(int argc, char **argv) { return uya_main(argc, argv); }
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
/* Movie playback (RAC1 Wrapper).
|
||||
*
|
||||
* The game plays its movies (the intro, the cut-scenes) with Sony's sample
|
||||
* player (src/game/movie: disc streaming, the IPU, EE threads, the SPU),
|
||||
* entered through func_0023B670 (host/overrides). On the PC the whole call is
|
||||
* replaced: the movie's sectors are read from the disc image, the MPEG-2
|
||||
* video is decoded with pl_mpeg2.h and shown at the movie's frame rate with
|
||||
* its sound (PS2 ADPCM, the language track the game asks for). A skip key
|
||||
* ends it early, like a pad button on the PS2. Shared with the UYA Wrapper. */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#define PL_MPEG_IMPLEMENTATION
|
||||
#include "pl_mpeg2.h"
|
||||
#include "pss.h"
|
||||
#include "uya_runtime.h"
|
||||
|
||||
int uya_movie_play_track(uint32_t lsn, uint32_t bytes, int track)
|
||||
{
|
||||
uint32_t sectors = (bytes + 2047) / 2048;
|
||||
uint8_t *ps = malloc((size_t)sectors * 2048), *es = 0;
|
||||
int skipped = 0, frames = 0;
|
||||
if (!ps || uya_disc_read(lsn, sectors, ps) != 0) { uya_log("movie at LSN %u: disc read failed", lsn); free(ps); return 0; }
|
||||
es = malloc((size_t)sectors * 2048);
|
||||
size_t n = pss_extract_video(ps, (size_t)sectors * 2048, es, (size_t)sectors * 2048);
|
||||
int16_t *pcm = 0;
|
||||
size_t pcm_frames = 0;
|
||||
int rate = 0;
|
||||
{
|
||||
uint8_t *au = malloc((size_t)sectors * 2048);
|
||||
size_t an = au ? pss_extract_audio(ps, (size_t)sectors * 2048, track, au, (size_t)sectors * 2048) : 0;
|
||||
if (!an && track && au) an = pss_extract_audio(ps, (size_t)sectors * 2048, 0, au, (size_t)sectors * 2048);
|
||||
if (an) pcm_frames = pss_decode_audio(au, an, &pcm, &rate);
|
||||
free(au);
|
||||
}
|
||||
free(ps);
|
||||
plm_video_t *v = plm_video_create_with_buffer(plm_buffer_create_with_memory(es, n, 0), 1);
|
||||
int w = plm_video_get_width(v), h = plm_video_get_height(v);
|
||||
double fps = plm_video_get_framerate(v);
|
||||
if (w <= 0 || fps <= 0) { uya_log("movie at LSN %u: no video", lsn); plm_video_destroy(v); free(es); return 0; }
|
||||
uya_log("movie at LSN %u: %ux%u at %.2f fps, %u KB; sound track %d: %s%.1f s at %d Hz", lsn, w, h, fps, (unsigned)(n / 1024), track,
|
||||
pcm_frames ? "" : "none ", pcm_frames && rate ? (double)pcm_frames / rate : 0.0, rate);
|
||||
uint32_t *px = malloc((size_t)w * h * 4);
|
||||
char tag[32];
|
||||
snprintf(tag, sizeof tag, "movie_%u", lsn);
|
||||
if (pcm_frames) plat_audio_play(pcm, pcm_frames, rate, tag);
|
||||
plat_movie_started();
|
||||
uint64_t t0 = plat_time_us();
|
||||
int nowait = getenv("UYA_NOWAIT") != 0, dropped = 0;
|
||||
plm_frame_t *f;
|
||||
while ((f = plm_video_decode(v))) {
|
||||
uint64_t due = t0 + (uint64_t)(frames * 1000000.0 / fps), now = plat_time_us();
|
||||
int late = !nowait && now > due + (uint64_t)(1000000.0 / fps); /* a frame behind the sound: not shown, to catch up */
|
||||
if (due > now && !nowait) plat_sleep_us(due - now);
|
||||
if (!late) {
|
||||
plm_frame_to_bgra(f, (uint8_t *)px, w * 4);
|
||||
{ /* GL mode: the GS thread draws it into the window */
|
||||
extern int uya_gs_movie_frame(const uint32_t *px, int w, int h);
|
||||
int shown = uya_gs_movie_frame(px, w, h);
|
||||
if (!plat_present(shown ? 0 : px, w, h)) { uya_log("window closed"); exit(0); }
|
||||
}
|
||||
} else dropped++;
|
||||
frames++;
|
||||
if (plat_key_skip()) { skipped = 1; break; }
|
||||
}
|
||||
plat_audio_stop();
|
||||
free(pcm);
|
||||
uya_log("movie at LSN %u: %d frames shown%s (%d dropped to keep up with the sound)", lsn, frames - dropped, skipped ? " (skipped)" : "", dropped);
|
||||
free(px);
|
||||
plm_video_destroy(v);
|
||||
free(es);
|
||||
return skipped;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/* pss.h: MPEG-2 program stream (PS2 .pss) demuxer: pulls the video elementary
|
||||
* stream (stream id 0xE0) out of pack/PES framing. */
|
||||
#ifndef UYA_PSS_H
|
||||
#define UYA_PSS_H
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* Appends the payload of every video PES packet in `ps` to `out` (capacity
|
||||
* `cap`); returns the number of bytes written. */
|
||||
static size_t pss_extract_video(const uint8_t *ps, size_t n, uint8_t *out, size_t cap)
|
||||
{
|
||||
size_t i = 0, w = 0;
|
||||
while (i + 4 <= n) {
|
||||
if (ps[i] != 0 || ps[i + 1] != 0 || ps[i + 2] != 1) { i++; continue; }
|
||||
uint8_t id = ps[i + 3];
|
||||
if (id == 0xBA) { /* pack header */
|
||||
if ((ps[i + 4] & 0xC0) == 0x40) i += 14 + (ps[i + 13] & 7); /* MPEG-2 */
|
||||
else i += 12; /* MPEG-1 */
|
||||
continue;
|
||||
}
|
||||
if (id == 0xB9) { i += 4; continue; } /* program end */
|
||||
if (id < 0xBB) { i++; continue; } /* not a PES/system packet */
|
||||
if (i + 6 > n) break;
|
||||
size_t len = ((size_t)ps[i + 4] << 8) | ps[i + 5];
|
||||
size_t end = i + 6 + len;
|
||||
if (end > n) end = n;
|
||||
if (id == 0xE0 && (ps[i + 6] & 0xC0) == 0x80) { /* MPEG-2 PES header */
|
||||
size_t p = i + 9 + ps[i + 8];
|
||||
if (p < end && w + (end - p) <= cap) { memcpy(out + w, ps + p, end - p); w += end - p; }
|
||||
}
|
||||
i = end;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
/* Audio: private stream 1 (0xBD). Each payload starts with a 4-byte substream
|
||||
* header (ff a1 00 0N, N = the track; the movies carry several language
|
||||
* tracks). The track data is an "SShd" header then "SSbd" and PS2 ADPCM.
|
||||
* Appends track `track`'s bytes to `out`; returns the count. */
|
||||
static size_t pss_extract_audio(const uint8_t *ps, size_t n, int track, uint8_t *out, size_t cap)
|
||||
{
|
||||
size_t i = 0, w = 0;
|
||||
while (i + 4 <= n) {
|
||||
if (ps[i] != 0 || ps[i + 1] != 0 || ps[i + 2] != 1) { i++; continue; }
|
||||
uint8_t id = ps[i + 3];
|
||||
if (id == 0xBA) { i += ((ps[i + 4] & 0xC0) == 0x40) ? 14 + (ps[i + 13] & 7) : 12; continue; }
|
||||
if (id == 0xB9) { i += 4; continue; }
|
||||
if (id < 0xBB) { i++; continue; }
|
||||
if (i + 6 > n) break;
|
||||
size_t len = ((size_t)ps[i + 4] << 8) | ps[i + 5];
|
||||
size_t end = i + 6 + len;
|
||||
if (end > n) end = n;
|
||||
if (id == 0xBD && (ps[i + 6] & 0xC0) == 0x80) {
|
||||
size_t p = i + 9 + ps[i + 8];
|
||||
if (p + 4 <= end && ps[p] == 0xFF && ps[p + 3] == track) {
|
||||
p += 4;
|
||||
if (w + (end - p) <= cap) { memcpy(out + w, ps + p, end - p); w += end - p; }
|
||||
}
|
||||
}
|
||||
i = end;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
static uint32_t pss_le32(const uint8_t *p) { return p[0] | (p[1] << 8) | (p[2] << 16) | ((uint32_t)p[3] << 24); }
|
||||
|
||||
/* Decodes an SShd/SSbd track (PS2 ADPCM, channels interleaved in blocks of
|
||||
* `interleave` bytes) into interleaved 16-bit stereo. Returns frames, or 0. */
|
||||
static size_t pss_decode_audio(const uint8_t *a, size_t n, int16_t **out, int *rate)
|
||||
{
|
||||
static const int fk[5][2] = {{0, 0}, {60, 0}, {115, -52}, {98, -55}, {122, -60}};
|
||||
if (n < 0x28 || memcmp(a, "SShd", 4) || pss_le32(a + 8) != 0x10) return 0;
|
||||
uint32_t hsz = pss_le32(a + 4), ch = pss_le32(a + 16), il = pss_le32(a + 20);
|
||||
*rate = (int)pss_le32(a + 12);
|
||||
const uint8_t *b = a + 8 + hsz;
|
||||
if (b + 8 > a + n || memcmp(b, "SSbd", 4) || ch < 1 || ch > 2 || il < 16 || il % 16) return 0;
|
||||
size_t len = pss_le32(b + 4);
|
||||
b += 8;
|
||||
if (len > (size_t)(a + n - b)) len = (size_t)(a + n - b);
|
||||
size_t blocks = len / (il * ch), per = il / 16 * 28;
|
||||
size_t frames = blocks * per;
|
||||
int16_t *o = (int16_t *)malloc(frames * 4 + 4);
|
||||
if (!o) return 0;
|
||||
int hist[2][2] = {{0, 0}, {0, 0}};
|
||||
for (size_t k = 0; k < blocks; k++)
|
||||
for (uint32_t c = 0; c < ch; c++) {
|
||||
const uint8_t *blk = b + (k * ch + c) * il;
|
||||
for (uint32_t f = 0; f < il / 16; f++) {
|
||||
const uint8_t *fr = blk + f * 16;
|
||||
int sh = fr[0] & 15, flt = (fr[0] >> 4) & 7;
|
||||
if (flt > 4) flt = 0;
|
||||
if (sh > 12) sh = 9;
|
||||
for (int s = 0; s < 28; s++) {
|
||||
int nib = (fr[2 + s / 2] >> ((s & 1) * 4)) & 15;
|
||||
if (nib >= 8) nib -= 16;
|
||||
int v = (nib << 12) >> sh;
|
||||
v += (hist[c][0] * fk[flt][0] + hist[c][1] * fk[flt][1] + 32) >> 6;
|
||||
if (v > 32767) v = 32767;
|
||||
if (v < -32768) v = -32768;
|
||||
hist[c][1] = hist[c][0];
|
||||
hist[c][0] = v;
|
||||
size_t idx = (k * per + f * 28 + s) * 2;
|
||||
o[idx + c] = (int16_t)v;
|
||||
if (ch == 1) o[idx + 1] = (int16_t)v;
|
||||
}
|
||||
}
|
||||
}
|
||||
*out = o;
|
||||
return frames;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,280 @@
|
||||
/* Linux (i386) platform layer: fixed mappings and the int3 trap for calls
|
||||
* into PS2 code the host does not implement. */
|
||||
#ifdef __linux__
|
||||
#define _GNU_SOURCE
|
||||
#include "uya_runtime.h"
|
||||
#include <sys/mman.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <ucontext.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef MAP_FIXED_NOREPLACE
|
||||
#define MAP_FIXED_NOREPLACE 0x100000
|
||||
#endif
|
||||
|
||||
int plat_map_fixed(uint32_t addr, uint32_t size, int exec)
|
||||
{
|
||||
int prot = PROT_READ | PROT_WRITE | (exec ? PROT_EXEC : 0);
|
||||
void *p = mmap((void *)(uintptr_t)addr, size, prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
|
||||
if (p == MAP_FAILED || p != (void *)(uintptr_t)addr) {
|
||||
uya_log("cannot map 0x%08X+0x%X (%p)", addr, size, p);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void plat_flush_icache(void *p, size_t n) { (void)p; (void)n; /* x86 keeps I/D coherent */ }
|
||||
|
||||
const char *plat_exe_dir(void)
|
||||
{
|
||||
static char d[1024];
|
||||
if (!d[0]) {
|
||||
ssize_t n = readlink("/proc/self/exe", d, sizeof d - 1);
|
||||
if (n <= 0) { strcpy(d, "."); return d; }
|
||||
d[n] = 0;
|
||||
char *s = strrchr(d, '/');
|
||||
if (s) *s = 0;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
void plat_at_exit(void) {}
|
||||
|
||||
#include <time.h>
|
||||
uint64_t plat_time_us(void)
|
||||
{
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
return (uint64_t)t.tv_sec * 1000000u + t.tv_nsec / 1000;
|
||||
}
|
||||
|
||||
void plat_sleep_us(uint64_t us)
|
||||
{
|
||||
struct timespec t = { (time_t)(us / 1000000), (long)(us % 1000000) * 1000 };
|
||||
nanosleep(&t, 0);
|
||||
}
|
||||
|
||||
/* A call landed on an int3 (0xCC) byte in PS2 code space: log it, then return
|
||||
* to the caller as if the function returned 0. cdecl: the caller pops the
|
||||
* arguments, so popping the return address is all that is needed. */
|
||||
static void on_trap(int sig, siginfo_t *si, void *ctxv)
|
||||
{
|
||||
ucontext_t *uc = ctxv;
|
||||
greg_t *g = uc->uc_mcontext.gregs;
|
||||
uint32_t eip = (uint32_t)g[REG_EIP];
|
||||
uint32_t esp = (uint32_t)g[REG_ESP];
|
||||
uint32_t at = eip - 1; /* int3 already advanced eip */
|
||||
(void)si;
|
||||
if (sig != SIGTRAP || at < PS2_RAM_LO || at >= PS2_RAM_HI) {
|
||||
uya_log("fatal signal %d at eip=0x%08X", sig, eip);
|
||||
uya_report();
|
||||
_exit(1);
|
||||
}
|
||||
uint32_t ret = *(uint32_t *)(uintptr_t)esp;
|
||||
uya_trap_call(at, ret, (const uint32_t *)(uintptr_t)(esp + 4));
|
||||
g[REG_EAX] = 0;
|
||||
g[REG_EDX] = 0;
|
||||
g[REG_EIP] = ret;
|
||||
g[REG_ESP] = esp + 4;
|
||||
}
|
||||
|
||||
static void on_fault(int sig, siginfo_t *si, void *ctxv)
|
||||
{
|
||||
ucontext_t *uc = ctxv;
|
||||
uya_log("crash: signal %d, fault address %p, eip=0x%08X", sig, si->si_addr, (uint32_t)uc->uc_mcontext.gregs[REG_EIP]);
|
||||
uya_report();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
/* testing: UYA_PROF=file samples the program counter of whichever thread is
|
||||
* running every millisecond of CPU time and writes "eip tid count" lines at
|
||||
* exit (symbolize with nm; each thread's own pc, no call stacks) */
|
||||
#include <sys/time.h>
|
||||
#include <sys/syscall.h>
|
||||
#define PROF_N (1 << 16)
|
||||
static struct { uint32_t eip, tid, n; } prof_tab[PROF_N];
|
||||
static const char *prof_path;
|
||||
static void on_prof(int sig, siginfo_t *si, void *ctxv)
|
||||
{
|
||||
ucontext_t *uc = ctxv;
|
||||
uint32_t eip = (uint32_t)uc->uc_mcontext.gregs[REG_EIP], tid = (uint32_t)syscall(SYS_gettid);
|
||||
uint32_t h = (eip * 2654435761u ^ tid * 40503u) >> 16;
|
||||
(void)sig; (void)si;
|
||||
for (int k = 0; k < 64; k++) {
|
||||
uint32_t j = (h + k) & (PROF_N - 1);
|
||||
if (prof_tab[j].n && (prof_tab[j].eip != eip || prof_tab[j].tid != tid)) continue;
|
||||
prof_tab[j].eip = eip; prof_tab[j].tid = tid; __atomic_add_fetch(&prof_tab[j].n, 1, __ATOMIC_RELAXED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
static void prof_dump(void)
|
||||
{
|
||||
FILE *f = fopen(prof_path, "w");
|
||||
if (!f) return;
|
||||
for (int j = 0; j < PROF_N; j++) if (prof_tab[j].n) fprintf(f, "%08x %u %u\n", prof_tab[j].eip, prof_tab[j].tid, prof_tab[j].n);
|
||||
fclose(f);
|
||||
}
|
||||
static void prof_init(void)
|
||||
{
|
||||
prof_path = getenv("UYA_PROF");
|
||||
if (!prof_path) return;
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof sa);
|
||||
sa.sa_flags = SA_SIGINFO | SA_RESTART;
|
||||
sa.sa_sigaction = on_prof;
|
||||
sigaction(SIGPROF, &sa, 0);
|
||||
struct itimerval it = { { 0, 1000 }, { 0, 1000 } };
|
||||
setitimer(ITIMER_PROF, &it, 0);
|
||||
atexit(prof_dump);
|
||||
}
|
||||
|
||||
int plat_install_trap_handler(void)
|
||||
{
|
||||
prof_init();
|
||||
struct sigaction sa;
|
||||
static char altstack[64 * 1024];
|
||||
stack_t ss = { .ss_sp = altstack, .ss_size = sizeof altstack, .ss_flags = 0 };
|
||||
sigaltstack(&ss, 0);
|
||||
memset(&sa, 0, sizeof sa);
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
sa.sa_sigaction = on_trap;
|
||||
sigaction(SIGTRAP, &sa, 0);
|
||||
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
||||
sa.sa_sigaction = on_fault;
|
||||
sigaction(SIGSEGV, &sa, 0);
|
||||
sigaction(SIGBUS, &sa, 0);
|
||||
sigaction(SIGILL, &sa, 0);
|
||||
sigaction(SIGFPE, &sa, 0);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
/* No window on Linux (yet): with UYA_DUMP=dir every 15th presented frame is
|
||||
* written there as a PPM, so output can be checked. */
|
||||
#include <stdlib.h>
|
||||
int plat_present(const uint32_t *px, int w, int h)
|
||||
{
|
||||
static unsigned long n;
|
||||
const char *dir = getenv("UYA_DUMP");
|
||||
static long every = -1;
|
||||
if (every < 0) { const char *e = getenv("UYA_DUMP_EVERY"); every = e ? atol(e) : 15; if (every < 1) every = 1; }
|
||||
if (!px) return 1; /* no new frame this vsync */
|
||||
static long from = -1;
|
||||
if (from < 0) { const char *e = getenv("UYA_DUMP_FROM"); from = e ? atol(e) : 0; } /* testing: first vsync to dump */
|
||||
if (dir && (n % every) == 0 && uya_frame_count() >= (uint32_t)from) {
|
||||
char path[512];
|
||||
snprintf(path, sizeof path, "%s/frame_%05lu.ppm", dir, n);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (f) {
|
||||
fprintf(f, "P6 %d %d 255\n", w, h);
|
||||
for (int i = 0; i < w * h; i++) { uint32_t c = px[i]; fputc((c >> 16) & 0xFF, f); fputc((c >> 8) & 0xFF, f); fputc(c & 0xFF, f); /* BGRX in memory */ }
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
n++;
|
||||
return 1;
|
||||
}
|
||||
/* Scripted input for testing: UYA_KEYS="start@600+10,cross@900" presses a
|
||||
* button from vsync 600 for 10 vsyncs (default 6). During a movie the vsync
|
||||
* count stands still, so a press that is due skips the movie. */
|
||||
static const struct { const char *name; unsigned bit; } pad_names[] = {
|
||||
{"select", 0x0001}, {"start", 0x0008}, {"up", 0x0010}, {"right", 0x0020}, {"down", 0x0040}, {"left", 0x0080},
|
||||
{"l2", 0x0100}, {"r2", 0x0200}, {"l1", 0x0400}, {"r1", 0x0800},
|
||||
{"triangle", 0x1000}, {"circle", 0x2000}, {"cross", 0x4000}, {"square", 0x8000},
|
||||
/* the left stick pushed all the way (testing; libpad2's scePad2Read reports it) */
|
||||
{"lup", 0x10000}, {"ldown", 0x20000}, {"lleft", 0x40000}, {"lright", 0x80000},
|
||||
{"rup", 0x100000}, {"rdown", 0x200000}, {"rleft", 0x400000}, {"rright", 0x800000},
|
||||
};
|
||||
int plat_pad_sticks(uint8_t st[4]) { (void)st; return 0; }
|
||||
unsigned plat_pad_buttons(void)
|
||||
{
|
||||
const char *s = getenv("UYA_KEYS");
|
||||
unsigned now = uya_frame_count(), bits = 0;
|
||||
while (s && *s) {
|
||||
char name[16];
|
||||
unsigned at = 0, dur = 6;
|
||||
int k = 0;
|
||||
while (*s && *s != '@' && *s != ',' && k < 15) name[k++] = *s++;
|
||||
name[k] = 0;
|
||||
if (*s == '@') at = (unsigned)strtoul(s + 1, (char **)&s, 10);
|
||||
if (*s == '+') dur = (unsigned)strtoul(s + 1, (char **)&s, 10);
|
||||
if (now >= at && now < at + dur)
|
||||
for (unsigned i = 0; i < sizeof pad_names / sizeof pad_names[0]; i++)
|
||||
if (!strcmp(name, pad_names[i].name)) bits |= pad_names[i].bit;
|
||||
while (*s && *s != ',') s++;
|
||||
if (*s == ',') s++;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
/* UYA_SKIPMOVIES=n skips the first n movies (plat_movie_started counts them) */
|
||||
static int movies_started;
|
||||
void plat_movie_started(void) { movies_started++; }
|
||||
int plat_key_skip(void)
|
||||
{
|
||||
const char *e = getenv("UYA_SKIPMOVIES");
|
||||
if (e && movies_started <= atoi(e)) return 1;
|
||||
return (plat_pad_buttons() & 0x4008) != 0;
|
||||
}
|
||||
void plat_poll(void) {}
|
||||
|
||||
/* Sound on Linux: with UYA_DUMP set, each sound is saved there as a WAV file. */
|
||||
void plat_audio_play(const int16_t *lr, size_t frames, int rate, const char *tag)
|
||||
{
|
||||
const char *dir = getenv("UYA_DUMP");
|
||||
if (!dir) return;
|
||||
char path[512];
|
||||
snprintf(path, sizeof path, "%s/%s.wav", dir, tag);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return;
|
||||
uint32_t data = (uint32_t)(frames * 4), v;
|
||||
fwrite("RIFF", 1, 4, f); v = 36 + data; fwrite(&v, 4, 1, f);
|
||||
fwrite("WAVEfmt ", 1, 8, f); v = 16; fwrite(&v, 4, 1, f);
|
||||
uint16_t pcm = 1, ch = 2, ba = 4, bits = 16;
|
||||
fwrite(&pcm, 2, 1, f); fwrite(&ch, 2, 1, f);
|
||||
v = (uint32_t)rate; fwrite(&v, 4, 1, f); v = (uint32_t)rate * 4; fwrite(&v, 4, 1, f);
|
||||
fwrite(&ba, 2, 1, f); fwrite(&bits, 2, 1, f);
|
||||
fwrite("data", 1, 4, f); fwrite(&data, 4, 1, f);
|
||||
fwrite(lr, 4, frames, f);
|
||||
fclose(f);
|
||||
}
|
||||
void plat_audio_stop(void) {}
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
/* Sound effects on Linux: with UYA_DUMP set they go to sfx_48k_stereo.raw
|
||||
* (16-bit PCM). Playback is simulated with the clock so the mixer is paced. */
|
||||
static uint64_t stream_written, stream_t0;
|
||||
uint64_t uya_fixed_clock(void);
|
||||
static uint64_t stream_now(void) { uint64_t v = uya_fixed_clock(); return v ? v : plat_time_us(); } /* UYA_FIXEDTIME: the game's clock */
|
||||
void plat_stream_write(const int16_t *lr, int frames)
|
||||
{
|
||||
static FILE *f;
|
||||
static int tried;
|
||||
if (!tried) {
|
||||
const char *dir = getenv("UYA_DUMP");
|
||||
tried = 1;
|
||||
if (dir) { char path[512]; snprintf(path, sizeof path, "%s/sfx_48k_stereo.raw", dir); f = fopen(path, "wb"); }
|
||||
}
|
||||
if (!stream_t0) stream_t0 = stream_now();
|
||||
if (f) fwrite(lr, 4, (size_t)frames, f);
|
||||
stream_written += (uint64_t)frames;
|
||||
}
|
||||
int plat_stream_queued(void)
|
||||
{
|
||||
if (!stream_t0) return 0;
|
||||
uint64_t played = (stream_now() - stream_t0) * 48000 / 1000000;
|
||||
return played >= stream_written ? 0 : (int)(stream_written - played);
|
||||
}
|
||||
/* OpenGL: this build is 32-bit static (musl) and cannot load a GL driver, so
|
||||
* the GS draws in software (the GL renderer is tested with tools/gs_replay.c) */
|
||||
int plat_gl_active;
|
||||
int plat_gl_init(void) { return 0; }
|
||||
void plat_gl_current(int on) { (void)on; }
|
||||
void plat_gl_swap(void) {}
|
||||
void plat_gl_size(int *w, int *h) { *w = 640; *h = 480; }
|
||||
void *plat_gl_proc(const char *name) { (void)name; return 0; }
|
||||
#endif
|
||||
@@ -0,0 +1,739 @@
|
||||
/* Windows (x86) platform layer. The menu code is in rac1_game.dll; the
|
||||
* launcher (launcher/launcher_win32.c) is a tiny exe whose own image covers
|
||||
* PS2 RAM, so that range is reserved before Windows places the stack and heaps. */
|
||||
#ifdef _WIN32
|
||||
#include "uya_runtime.h"
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <shellapi.h>
|
||||
#include <mmsystem.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
extern int plat_gl_active; /* the GS presents with OpenGL (below) */
|
||||
|
||||
int plat_map_fixed(uint32_t addr, uint32_t size, int exec)
|
||||
{
|
||||
DWORD prot = exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
|
||||
void *p = VirtualAlloc((void *)(uintptr_t)addr, size, MEM_RESERVE | MEM_COMMIT, prot);
|
||||
if (p == (void *)(uintptr_t)addr) return 0;
|
||||
/* already there? (PS2 RAM comes from the launcher's image section) */
|
||||
MEMORY_BASIC_INFORMATION mi;
|
||||
uint32_t a = addr;
|
||||
while (a < addr + size) {
|
||||
if (!VirtualQuery((void *)(uintptr_t)a, &mi, sizeof mi) || mi.State != MEM_COMMIT) {
|
||||
uya_log("cannot map 0x%08X+0x%X (0x%08X is in use)", addr, size, a);
|
||||
return -1;
|
||||
}
|
||||
DWORD old;
|
||||
VirtualProtect(mi.BaseAddress, mi.RegionSize, prot, &old);
|
||||
a = (uint32_t)(uintptr_t)mi.BaseAddress + (uint32_t)mi.RegionSize;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void plat_flush_icache(void *p, size_t n) { FlushInstructionCache(GetCurrentProcess(), p, n); }
|
||||
|
||||
const char *plat_exe_dir(void)
|
||||
{
|
||||
static char d[MAX_PATH];
|
||||
if (!d[0]) {
|
||||
DWORD n = GetModuleFileNameA(0, d, sizeof d);
|
||||
if (!n) { strcpy(d, "."); return d; }
|
||||
char *s = strrchr(d, '\\');
|
||||
if (s) *s = 0;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
/* Started by double-click, the console belongs to this process alone and
|
||||
* would close at once; wait for a key so the log can be read. */
|
||||
void plat_at_exit(void)
|
||||
{
|
||||
static int done;
|
||||
DWORD pids[2];
|
||||
if (done++) return;
|
||||
if (GetConsoleProcessList(pids, 2) == 1) {
|
||||
fprintf(stderr, "\nPress Enter to close this window.\n");
|
||||
getchar();
|
||||
}
|
||||
}
|
||||
|
||||
static LONG CALLBACK on_exception(EXCEPTION_POINTERS *ep)
|
||||
{
|
||||
EXCEPTION_RECORD *er = ep->ExceptionRecord;
|
||||
CONTEXT *c = ep->ContextRecord;
|
||||
uint32_t at = (uint32_t)(uintptr_t)er->ExceptionAddress;
|
||||
if (er->ExceptionCode == EXCEPTION_BREAKPOINT && at >= PS2_RAM_LO && at < PS2_RAM_HI) {
|
||||
uint32_t ret = *(uint32_t *)(uintptr_t)c->Esp;
|
||||
uya_trap_call(at, ret, (const uint32_t *)(uintptr_t)(c->Esp + 4));
|
||||
c->Eax = 0; c->Edx = 0;
|
||||
c->Eip = ret; c->Esp += 4;
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
uya_log("crash: exception 0x%08lX at 0x%08X, fault address 0x%08lX", er->ExceptionCode, at,
|
||||
er->NumberParameters > 1 ? (unsigned long)er->ExceptionInformation[1] : 0ul);
|
||||
uya_log("crash: thread %lu, eax %08lX ecx %08lX edx %08lX ebx %08lX esi %08lX edi %08lX esp %08lX ebp %08lX",
|
||||
GetCurrentThreadId(), c->Eax, c->Ecx, c->Edx, c->Ebx, c->Esi, c->Edi, c->Esp, c->Ebp);
|
||||
{ /* the stack: return addresses near its top, then the frame chain */
|
||||
const uint32_t *sp = (const uint32_t *)(uintptr_t)c->Esp;
|
||||
for (int i = 0; i < 24; i += 8)
|
||||
uya_log("crash: stack +%02X: %08X %08X %08X %08X %08X %08X %08X %08X", i * 4, sp[i], sp[i + 1], sp[i + 2], sp[i + 3], sp[i + 4], sp[i + 5], sp[i + 6], sp[i + 7]);
|
||||
uint32_t bp = (uint32_t)c->Ebp;
|
||||
for (int i = 0; i < 12 && bp > 0x10000 && !IsBadReadPtr((void *)(uintptr_t)bp, 8); i++) {
|
||||
uya_log("crash: frame %d: return %08X", i, ((uint32_t *)(uintptr_t)bp)[1]);
|
||||
bp = ((uint32_t *)(uintptr_t)bp)[0];
|
||||
}
|
||||
}
|
||||
uya_report();
|
||||
plat_at_exit();
|
||||
ExitProcess(1);
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
int plat_install_trap_handler(void)
|
||||
{
|
||||
return AddVectoredExceptionHandler(1, on_exception) ? 0 : -1;
|
||||
}
|
||||
|
||||
uint64_t plat_time_us(void)
|
||||
{
|
||||
static LARGE_INTEGER f;
|
||||
LARGE_INTEGER c;
|
||||
if (!f.QuadPart) QueryPerformanceFrequency(&f);
|
||||
QueryPerformanceCounter(&c);
|
||||
return (uint64_t)(c.QuadPart / f.QuadPart) * 1000000u + (uint64_t)(c.QuadPart % f.QuadPart) * 1000000u / f.QuadPart;
|
||||
}
|
||||
|
||||
void plat_sleep_us(uint64_t us)
|
||||
{
|
||||
static int period;
|
||||
if (!period) { timeBeginPeriod(1); period = 1; } /* 1 ms sleep resolution */
|
||||
if (us >= 1000) Sleep((DWORD)(us / 1000));
|
||||
}
|
||||
|
||||
/* ---- the window ----
|
||||
* A plain Win32 window; frames are stretched into it with StretchDIBits
|
||||
* (4:3, 640x480 to start, resizable). */
|
||||
static HWND win;
|
||||
static volatile int win_closed;
|
||||
|
||||
/* Controller only: the keyboard does not play (F9 still records a glitch capture). */
|
||||
|
||||
/* XInput gamepads (loaded at run time: no XInput, no gamepad) */
|
||||
typedef struct { WORD wButtons; BYTE bLeftTrigger, bRightTrigger; SHORT sThumbLX, sThumbLY, sThumbRX, sThumbRY; } xi_pad_t;
|
||||
typedef struct { DWORD dwPacketNumber; xi_pad_t Gamepad; } xi_state_t;
|
||||
typedef DWORD (WINAPI *xi_get_t)(DWORD, xi_state_t *);
|
||||
static xi_get_t xi_get;
|
||||
static int xi_tried;
|
||||
static int xi_read(xi_state_t *st)
|
||||
{
|
||||
if (!xi_tried) {
|
||||
static const char *dlls[] = { "xinput1_4.dll", "xinput1_3.dll", "xinput9_1_0.dll" };
|
||||
xi_tried = 1;
|
||||
for (int i = 0; i < 3 && !xi_get; i++) { HMODULE m = LoadLibraryA(dlls[i]); if (m) xi_get = (xi_get_t)(void *)GetProcAddress(m, "XInputGetState"); }
|
||||
if (xi_get) uya_log("gamepad: XInput available");
|
||||
}
|
||||
if (!xi_get) return 0;
|
||||
static uint64_t next_try; /* a missing pad is slow to poll: try it again every second */
|
||||
uint64_t t = plat_time_us();
|
||||
if (next_try && t < next_try) return 0;
|
||||
if (xi_get(0, st) != 0) { next_try = t + 1000000; return 0; }
|
||||
next_try = 0;
|
||||
return 1;
|
||||
}
|
||||
static unsigned xi_buttons(const xi_pad_t *g)
|
||||
{
|
||||
static const struct { WORD xi; unsigned ps2; } map[] = {
|
||||
{0x0001, 0x0010}, {0x0002, 0x0040}, {0x0004, 0x0080}, {0x0008, 0x0020}, /* d-pad up down left right */
|
||||
{0x0010, 0x0008}, {0x0020, 0x0001}, /* Start, Back = Select */
|
||||
{0x0040, 0x0002}, {0x0080, 0x0004}, /* L3, R3 */
|
||||
{0x0100, 0x0400}, {0x0200, 0x0800}, /* LB = L1, RB = R1 */
|
||||
{0x1000, 0x4000}, {0x2000, 0x2000}, {0x4000, 0x8000}, {0x8000, 0x1000}, /* A = Cross, B = Circle, X = Square, Y = Triangle */
|
||||
};
|
||||
unsigned b = 0;
|
||||
for (unsigned i = 0; i < sizeof map / sizeof map[0]; i++) if (g->wButtons & map[i].xi) b |= map[i].ps2;
|
||||
if (g->bLeftTrigger > 64) b |= 0x0100; /* L2 */
|
||||
if (g->bRightTrigger > 64) b |= 0x0200; /* R2 */
|
||||
return b;
|
||||
}
|
||||
static uint8_t stick(SHORT v, int invert)
|
||||
{
|
||||
int x = v / 256; /* -128..127 */
|
||||
if (x > -24 && x < 24) return 0x7F; /* dead zone */
|
||||
if (invert) x = -x - 1;
|
||||
x += 0x80;
|
||||
return (uint8_t)(x < 0 ? 0 : x > 255 ? 255 : x);
|
||||
}
|
||||
/* ---- PlayStation controllers (DualShock 4, DualSense) over USB or Bluetooth ----
|
||||
* Read as plain HID devices (hid.dll and setupapi.dll loaded at run time) on
|
||||
* a thread of their own, which keeps the latest state. Reports handled:
|
||||
* DualShock 4: 0x01 (USB, and Bluetooth before the full mode) and 0x11
|
||||
* (Bluetooth full mode): LX LY RX RY, hat + face buttons, shoulder and
|
||||
* stick buttons, PS, L2 R2 analog, from offset 1 (0x01) or 3 (0x11)
|
||||
* DualSense: 0x01 over USB (64 bytes) and 0x31 over Bluetooth: LX LY RX RY
|
||||
* L2 R2, a counter, then the button bytes as the DualShock 4 has them;
|
||||
* its short Bluetooth 0x01 report has the DualShock 4 layout */
|
||||
typedef struct { ULONG Size; USHORT VendorID, ProductID, VersionNumber; } hid_attr_t; /* HIDD_ATTRIBUTES */
|
||||
typedef struct { DWORD cbSize; GUID InterfaceClassGuid; DWORD Flags; ULONG_PTR Reserved; } sp_ifdata_t;
|
||||
typedef struct { DWORD cbSize; CHAR DevicePath[1]; } sp_ifdetail_t;
|
||||
static void (WINAPI *p_HidD_GetHidGuid)(GUID *);
|
||||
static BOOLEAN (WINAPI *p_HidD_GetAttributes)(HANDLE, hid_attr_t *);
|
||||
static HANDLE (WINAPI *p_SetupDiGetClassDevsA)(const GUID *, PCSTR, HWND, DWORD);
|
||||
static BOOL (WINAPI *p_SetupDiEnumDeviceInterfaces)(HANDLE, void *, const GUID *, DWORD, sp_ifdata_t *);
|
||||
static BOOL (WINAPI *p_SetupDiGetDeviceInterfaceDetailA)(HANDLE, sp_ifdata_t *, sp_ifdetail_t *, DWORD, DWORD *, void *);
|
||||
static BOOL (WINAPI *p_SetupDiDestroyDeviceInfoList)(HANDLE);
|
||||
static volatile LONG ds_ok; /* a controller is connected and reporting */
|
||||
static volatile unsigned ds_buttons; /* PS2 pad bits */
|
||||
static volatile uint8_t ds_sticks[4]; /* LX LY RX RY, 0x80 centre, down = +Y as on the PS2 */
|
||||
|
||||
static HANDLE ds_open(int *dualsense)
|
||||
{
|
||||
GUID g; p_HidD_GetHidGuid(&g);
|
||||
HANDLE set = p_SetupDiGetClassDevsA(&g, 0, 0, 0x02 | 0x10); /* DIGCF_PRESENT | DIGCF_DEVICEINTERFACE */
|
||||
if (set == INVALID_HANDLE_VALUE) return INVALID_HANDLE_VALUE;
|
||||
HANDLE found = INVALID_HANDLE_VALUE;
|
||||
static int logged; /* the first scan is logged: which HID devices there are, and why one was not used */
|
||||
int ndev = 0;
|
||||
for (DWORD i = 0; found == INVALID_HANDLE_VALUE; i++) {
|
||||
sp_ifdata_t ifd; memset(&ifd, 0, sizeof ifd); ifd.cbSize = sizeof ifd;
|
||||
if (!p_SetupDiEnumDeviceInterfaces(set, 0, &g, i, &ifd)) break;
|
||||
DWORD need = 0;
|
||||
p_SetupDiGetDeviceInterfaceDetailA(set, &ifd, 0, 0, &need, 0);
|
||||
if (!need) continue;
|
||||
sp_ifdetail_t *det = malloc(need);
|
||||
if (!det) continue;
|
||||
det->cbSize = sizeof(DWORD) + sizeof(CHAR); /* 5 on 32-bit Windows */
|
||||
if (p_SetupDiGetDeviceInterfaceDetailA(set, &ifd, det, need, 0, 0)) {
|
||||
ndev++;
|
||||
{ /* the path names the vendor and product: only Sony devices are opened */
|
||||
char low[512]; size_t k = 0;
|
||||
for (; det->DevicePath[k] && k < sizeof low - 1; k++) low[k] = (char)tolower((unsigned char)det->DevicePath[k]);
|
||||
low[k] = 0;
|
||||
int sony = strstr(low, "054c") != 0;
|
||||
if (!logged && sony) uya_log("gamepad: HID device %s", det->DevicePath);
|
||||
if (!sony) { free(det); continue; }
|
||||
}
|
||||
HANDLE h = CreateFileA(det->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
|
||||
if (h == INVALID_HANDLE_VALUE) /* some systems open game controllers for reading only */
|
||||
h = CreateFileA(det->DevicePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
|
||||
if (h == INVALID_HANDLE_VALUE && !logged) uya_log("gamepad: cannot open it (error %lu)", GetLastError());
|
||||
if (h != INVALID_HANDLE_VALUE) {
|
||||
hid_attr_t at; memset(&at, 0, sizeof at); at.Size = sizeof at;
|
||||
int ok = 0;
|
||||
if (p_HidD_GetAttributes(h, &at) && at.VendorID == 0x054C) {
|
||||
if (at.ProductID == 0x05C4 || at.ProductID == 0x09CC || at.ProductID == 0x0BA0) { ok = 1; *dualsense = 0; } /* DualShock 4 (and its USB adapter) */
|
||||
if (at.ProductID == 0x0CE6 || at.ProductID == 0x0DF2) { ok = 1; *dualsense = 1; } /* DualSense (Edge) */
|
||||
}
|
||||
if (!logged) uya_log("gamepad: 054C:%04X%s", at.ProductID, ok ? " (a DualShock 4 / DualSense)" : " (not a known controller)");
|
||||
if (ok) { found = h; uya_log("gamepad: %s (054C:%04X) found", *dualsense ? "DualSense" : "DualShock 4", at.ProductID); }
|
||||
else CloseHandle(h);
|
||||
}
|
||||
}
|
||||
free(det);
|
||||
}
|
||||
p_SetupDiDestroyDeviceInfoList(set);
|
||||
if (!logged) uya_log("gamepad: %d HID devices, %s", ndev, found != INVALID_HANDLE_VALUE ? "a PlayStation controller is used" : "no PlayStation controller found (connected later, it is picked up)");
|
||||
logged = 1;
|
||||
return found;
|
||||
}
|
||||
|
||||
/* PS2 bits from the two button bytes the DualShock 4 and DualSense share, and the PS byte */
|
||||
static unsigned ds_map(uint8_t b0, uint8_t b1)
|
||||
{
|
||||
static const unsigned hat[9] = { 0x10, 0x10 | 0x20, 0x20, 0x20 | 0x40, 0x40, 0x40 | 0x80, 0x80, 0x80 | 0x10, 0 };
|
||||
unsigned b = hat[(b0 & 15) > 8 ? 8 : (b0 & 15)];
|
||||
if (b0 & 0x10) b |= 0x8000; /* Square */
|
||||
if (b0 & 0x20) b |= 0x4000; /* Cross */
|
||||
if (b0 & 0x40) b |= 0x2000; /* Circle */
|
||||
if (b0 & 0x80) b |= 0x1000; /* Triangle */
|
||||
if (b1 & 0x01) b |= 0x0400; /* L1 */
|
||||
if (b1 & 0x02) b |= 0x0800; /* R1 */
|
||||
if (b1 & 0x04) b |= 0x0100; /* L2 */
|
||||
if (b1 & 0x08) b |= 0x0200; /* R2 */
|
||||
if (b1 & 0x10) b |= 0x0001; /* Share / Create = Select */
|
||||
if (b1 & 0x20) b |= 0x0008; /* Options = Start */
|
||||
if (b1 & 0x40) b |= 0x0002; /* L3 */
|
||||
if (b1 & 0x80) b |= 0x0004; /* R3 */
|
||||
return b;
|
||||
}
|
||||
|
||||
static int ds_parse(const uint8_t *r, DWORD n, int dualsense)
|
||||
{
|
||||
const uint8_t *s; /* the sticks */
|
||||
uint8_t b0, b1;
|
||||
if (n < 10) return 0;
|
||||
if (r[0] == 0x11 && n >= 12) { s = r + 3; b0 = s[4]; b1 = s[5]; } /* DualShock 4, Bluetooth full */
|
||||
else if (r[0] == 0x31 && n >= 13) { s = r + 2; b0 = s[7]; b1 = s[8]; } /* DualSense, Bluetooth full */
|
||||
else if (r[0] == 0x01 && dualsense && n >= 12 && n > 20) { s = r + 1; b0 = s[7]; b1 = s[8]; } /* DualSense, USB */
|
||||
else if (r[0] == 0x01) { s = r + 1; b0 = s[4]; b1 = s[5]; } /* DualShock 4; DualSense short Bluetooth */
|
||||
else return 0;
|
||||
for (int i = 0; i < 4; i++) ds_sticks[i] = s[i];
|
||||
ds_buttons = ds_map(b0, b1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static DWORD WINAPI ds_thread(LPVOID a)
|
||||
{
|
||||
(void)a;
|
||||
static uint8_t buf[1024];
|
||||
for (;;) {
|
||||
int dualsense = 0;
|
||||
HANDLE h = ds_open(&dualsense);
|
||||
if (h == INVALID_HANDLE_VALUE) { Sleep(2000); continue; }
|
||||
{ /* asking a DualShock 4 on Bluetooth for its calibration switches it to the full reports (0x11) */
|
||||
static BOOLEAN (WINAPI *getf)(HANDLE, void *, ULONG);
|
||||
if (!getf) { HMODULE m = GetModuleHandleA("hid.dll"); if (m) getf = (void *)GetProcAddress(m, "HidD_GetFeature"); }
|
||||
if (getf && !dualsense) { uint8_t f[64]; memset(f, 0, sizeof f); f[0] = 0x05; getf(h, f, sizeof f); }
|
||||
}
|
||||
DWORD rlen = sizeof buf; /* one report per read (a longer buffer gets several, oldest first) */
|
||||
{
|
||||
HMODULE m = GetModuleHandleA("hid.dll");
|
||||
BOOLEAN (WINAPI *gp)(HANDLE, void **) = m ? (void *)GetProcAddress(m, "HidD_GetPreparsedData") : 0;
|
||||
BOOLEAN (WINAPI *fp)(void *) = m ? (void *)GetProcAddress(m, "HidD_FreePreparsedData") : 0;
|
||||
LONG (WINAPI *caps)(void *, void *) = m ? (void *)GetProcAddress(m, "HidP_GetCaps") : 0;
|
||||
void *pd = 0;
|
||||
if (gp && fp && caps && gp(h, &pd)) {
|
||||
USHORT c[32]; memset(c, 0, sizeof c);
|
||||
if (caps(pd, c) == 0x00110000 && c[2] && c[2] <= sizeof buf) rlen = c[2]; /* HIDP_STATUS_SUCCESS; InputReportByteLength */
|
||||
fp(pd);
|
||||
}
|
||||
uya_log("gamepad: reports of %lu bytes", rlen);
|
||||
}
|
||||
int fails = 0;
|
||||
for (;;) {
|
||||
DWORD n = 0;
|
||||
if (!ReadFile(h, buf, rlen, &n, 0)) { uya_log("gamepad: read failed (error %lu)", GetLastError()); if (++fails > 3) break; Sleep(5); continue; }
|
||||
fails = 0;
|
||||
{ static int nlog;
|
||||
if (nlog < 3) { nlog++; uya_log("gamepad: report %02X, %lu bytes: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", buf[0], n, buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10]); }
|
||||
}
|
||||
if (ds_parse(buf, n, dualsense)) InterlockedExchange(&ds_ok, 1);
|
||||
}
|
||||
InterlockedExchange(&ds_ok, 0);
|
||||
ds_buttons = 0;
|
||||
CloseHandle(h);
|
||||
uya_log("gamepad: PlayStation controller disconnected");
|
||||
Sleep(1000);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ds_start(void)
|
||||
{
|
||||
static int tried;
|
||||
if (tried) return;
|
||||
tried = 1;
|
||||
HMODULE hid = LoadLibraryA("hid.dll"), sa = LoadLibraryA("setupapi.dll");
|
||||
if (!hid || !sa) return;
|
||||
p_HidD_GetHidGuid = (void *)GetProcAddress(hid, "HidD_GetHidGuid");
|
||||
p_HidD_GetAttributes = (void *)GetProcAddress(hid, "HidD_GetAttributes");
|
||||
p_SetupDiGetClassDevsA = (void *)GetProcAddress(sa, "SetupDiGetClassDevsA");
|
||||
p_SetupDiEnumDeviceInterfaces = (void *)GetProcAddress(sa, "SetupDiEnumDeviceInterfaces");
|
||||
p_SetupDiGetDeviceInterfaceDetailA = (void *)GetProcAddress(sa, "SetupDiGetDeviceInterfaceDetailA");
|
||||
p_SetupDiDestroyDeviceInfoList = (void *)GetProcAddress(sa, "SetupDiDestroyDeviceInfoList");
|
||||
if (!p_HidD_GetHidGuid || !p_HidD_GetAttributes || !p_SetupDiGetClassDevsA || !p_SetupDiEnumDeviceInterfaces ||
|
||||
!p_SetupDiGetDeviceInterfaceDetailA || !p_SetupDiDestroyDeviceInfoList) return;
|
||||
HANDLE t = CreateThread(0, 0, ds_thread, 0, 0, 0);
|
||||
if (t) CloseHandle(t);
|
||||
}
|
||||
|
||||
static uint8_t ds_axis(uint8_t v) /* a small dead zone; the game has its own on top */
|
||||
{
|
||||
return (v > 0x80 - 12 && v < 0x80 + 12) ? 0x7F : v;
|
||||
}
|
||||
|
||||
/* The same controllers through the Windows joystick API (winmm), when the
|
||||
* HID reader does not get them: Windows itself reads a DualShock 4 or
|
||||
* DualSense as a game controller, axes X Y = left stick, Z R = right stick,
|
||||
* U V = L2 R2, buttons 1-14 Square Cross Circle Triangle L1 R1 L2 R2
|
||||
* Share Options L3 R3 PS touchpad, the d-pad as the POV hat. */
|
||||
static int jm_id = -1;
|
||||
static uint64_t jm_next;
|
||||
static int jm_read(JOYINFOEX *ji)
|
||||
{
|
||||
uint64_t t = plat_time_us();
|
||||
if (jm_id < 0) {
|
||||
if (t < jm_next) return 0;
|
||||
jm_next = t + 2000000; /* looked for every 2 s */
|
||||
UINT n = joyGetNumDevs();
|
||||
for (UINT i = 0; i < n && i < 16; i++) {
|
||||
JOYCAPSA cp; memset(&cp, 0, sizeof cp);
|
||||
if (joyGetDevCapsA(i, &cp, sizeof cp) != JOYERR_NOERROR) continue;
|
||||
JOYINFOEX tj; memset(&tj, 0, sizeof tj); tj.dwSize = sizeof tj; tj.dwFlags = JOY_RETURNALL;
|
||||
if (joyGetPosEx(i, &tj) != JOYERR_NOERROR) continue;
|
||||
uya_log("gamepad: joystick %u: %04X:%04X \"%s\", %u axes, %u buttons%s", i, cp.wMid, cp.wPid, cp.szPname, cp.wNumAxes, cp.wNumButtons,
|
||||
cp.wMid == 0x054C ? " (a PlayStation controller: used)" : "");
|
||||
if (cp.wMid == 0x054C) { jm_id = (int)i; break; }
|
||||
}
|
||||
if (jm_id < 0) return 0;
|
||||
}
|
||||
memset(ji, 0, sizeof *ji); ji->dwSize = sizeof *ji; ji->dwFlags = JOY_RETURNALL;
|
||||
if (joyGetPosEx((UINT)jm_id, ji) != JOYERR_NOERROR) { uya_log("gamepad: joystick %d gone", jm_id); jm_id = -1; jm_next = t + 2000000; return 0; }
|
||||
return 1;
|
||||
}
|
||||
static uint8_t jm_axis(DWORD v) { return ds_axis((uint8_t)(v >> 8)); } /* 0..65535 -> 0..255, 0x80 centre */
|
||||
static unsigned jm_buttons(const JOYINFOEX *ji)
|
||||
{
|
||||
static const unsigned map[14] = { 0x8000, 0x4000, 0x2000, 0x1000, 0x0400, 0x0800, 0x0100, 0x0200, 0x0001, 0x0008, 0x0002, 0x0004, 0, 0 };
|
||||
unsigned b = 0;
|
||||
for (int i = 0; i < 14; i++) if (ji->dwButtons & (1u << i)) b |= map[i];
|
||||
DWORD pov = ji->dwPOV;
|
||||
if (pov != 0xFFFF && pov <= 36000) {
|
||||
int o = (int)((pov + 2250) / 4500) % 8; /* 0 up, 1 up-right, ... clockwise */
|
||||
static const unsigned dir[8] = { 0x10, 0x30, 0x20, 0x60, 0x40, 0xC0, 0x80, 0x90 };
|
||||
b |= dir[o];
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
int plat_pad_sticks(uint8_t st[4])
|
||||
{
|
||||
ds_start();
|
||||
if (ds_ok) {
|
||||
st[0] = ds_axis(ds_sticks[0]); st[1] = ds_axis(ds_sticks[1]);
|
||||
st[2] = ds_axis(ds_sticks[2]); st[3] = ds_axis(ds_sticks[3]);
|
||||
return 1;
|
||||
}
|
||||
xi_state_t s;
|
||||
if (xi_read(&s)) {
|
||||
st[0] = stick(s.Gamepad.sThumbLX, 0); st[1] = stick(s.Gamepad.sThumbLY, 1); /* PS2: down is +Y */
|
||||
st[2] = stick(s.Gamepad.sThumbRX, 0); st[3] = stick(s.Gamepad.sThumbRY, 1);
|
||||
return 1;
|
||||
}
|
||||
JOYINFOEX ji;
|
||||
if (jm_read(&ji)) {
|
||||
st[0] = jm_axis(ji.dwXpos); st[1] = jm_axis(ji.dwYpos);
|
||||
st[2] = jm_axis(ji.dwZpos); st[3] = jm_axis(ji.dwRpos);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* testing: UYA_KEYS="start@600+10,cross@900" presses buttons from vsync 600 for 10 vsyncs (as on Linux) */
|
||||
static unsigned test_keys(void)
|
||||
{
|
||||
static const struct { const char *name; unsigned bit; } nm[] = {
|
||||
{"select", 0x0001}, {"start", 0x0008}, {"up", 0x0010}, {"right", 0x0020}, {"down", 0x0040}, {"left", 0x0080},
|
||||
{"l2", 0x0100}, {"r2", 0x0200}, {"l1", 0x0400}, {"r1", 0x0800},
|
||||
{"triangle", 0x1000}, {"circle", 0x2000}, {"cross", 0x4000}, {"square", 0x8000},
|
||||
{"lup", 0x10000}, {"ldown", 0x20000}, {"lleft", 0x40000}, {"lright", 0x80000},
|
||||
};
|
||||
static const char *keys; static int init;
|
||||
if (!init) { init = 1; keys = getenv("UYA_KEYS"); }
|
||||
const char *s = keys;
|
||||
unsigned now = uya_frame_count(), bits = 0;
|
||||
while (s && *s) {
|
||||
char name[16]; unsigned at = 0, dur = 6; int k = 0;
|
||||
while (*s && *s != '@' && *s != ',' && k < 15) name[k++] = *s++;
|
||||
name[k] = 0;
|
||||
if (*s == '@') at = (unsigned)strtoul(s + 1, (char **)&s, 10);
|
||||
if (*s == '+') dur = (unsigned)strtoul(s + 1, (char **)&s, 10);
|
||||
if (now >= at && now < at + dur)
|
||||
for (unsigned i = 0; i < sizeof nm / sizeof nm[0]; i++) if (!strcmp(name, nm[i].name)) bits |= nm[i].bit;
|
||||
while (*s && *s != ',') s++;
|
||||
if (*s == ',') s++;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
unsigned plat_pad_buttons(void)
|
||||
{
|
||||
xi_state_t s;
|
||||
JOYINFOEX ji;
|
||||
unsigned b = test_keys();
|
||||
ds_start();
|
||||
if (ds_ok) b |= ds_buttons;
|
||||
else if (xi_read(&s)) b |= xi_buttons(&s.Gamepad);
|
||||
else if (jm_read(&ji)) b |= jm_buttons(&ji);
|
||||
return b;
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK wnd_proc(HWND h, UINT m, WPARAM w, LPARAM l)
|
||||
{
|
||||
switch (m) {
|
||||
case WM_CLOSE: win_closed = 1; DestroyWindow(h); return 0;
|
||||
case WM_KEYDOWN:
|
||||
if (w == VK_F9 && !(l & (1 << 30))) { void uya_gs_rec_hotkey(void); uya_gs_rec_hotkey(); return 0; } /* testing: record the GS input */
|
||||
return 0;
|
||||
case WM_ERASEBKGND:
|
||||
if (plat_gl_active) return 1; /* GL draws the whole window */
|
||||
break;
|
||||
}
|
||||
return DefWindowProcA(h, m, w, l);
|
||||
}
|
||||
|
||||
static void pump(void)
|
||||
{
|
||||
MSG msg;
|
||||
while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessageA(&msg); }
|
||||
}
|
||||
|
||||
static void win_create(void)
|
||||
{
|
||||
if (win) return;
|
||||
WNDCLASSA wc = {0};
|
||||
RECT r = {0, 0, 640, 480};
|
||||
wc.style = CS_OWNDC; /* (OpenGL keeps the window's DC) */
|
||||
wc.lpfnWndProc = wnd_proc;
|
||||
wc.hInstance = GetModuleHandleA(0);
|
||||
wc.hCursor = LoadCursorA(0, (LPCSTR)IDC_ARROW);
|
||||
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
||||
wc.lpszClassName = "UYAWrapper";
|
||||
RegisterClassA(&wc);
|
||||
AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE);
|
||||
win = CreateWindowA("UYAWrapper", "Project Solana", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
r.right - r.left, r.bottom - r.top, 0, 0, wc.hInstance, 0);
|
||||
}
|
||||
|
||||
/* ---- OpenGL (the GS's GPU renderer, host/src/gs/gs_gl.c) ----
|
||||
* A 3.3 core context on the window through WGL; opengl32.dll is loaded at
|
||||
* run time, so a machine without it just keeps the software renderer. */
|
||||
int plat_gl_active;
|
||||
static HMODULE gl_lib;
|
||||
static HDC gl_dc;
|
||||
static HGLRC gl_rc;
|
||||
static HGLRC (WINAPI *p_wglCreateContext)(HDC);
|
||||
static BOOL (WINAPI *p_wglMakeCurrent)(HDC, HGLRC);
|
||||
static BOOL (WINAPI *p_wglDeleteContext)(HGLRC);
|
||||
static PROC (WINAPI *p_wglGetProcAddress)(LPCSTR);
|
||||
void *plat_gl_proc(const char *name)
|
||||
{
|
||||
void *p = p_wglGetProcAddress ? (void *)p_wglGetProcAddress(name) : 0;
|
||||
if (p == (void *)1 || p == (void *)2 || p == (void *)3 || p == (void *)-1) p = 0;
|
||||
if (!p && gl_lib) p = (void *)GetProcAddress(gl_lib, name); /* the GL 1.1 functions */
|
||||
return p;
|
||||
}
|
||||
int plat_gl_init(void)
|
||||
{
|
||||
win_create();
|
||||
if (!win) return 0;
|
||||
gl_lib = LoadLibraryA("opengl32.dll");
|
||||
if (!gl_lib) { uya_log("GL: no opengl32.dll"); return 0; }
|
||||
p_wglCreateContext = (HGLRC (WINAPI *)(HDC))GetProcAddress(gl_lib, "wglCreateContext");
|
||||
p_wglMakeCurrent = (BOOL (WINAPI *)(HDC, HGLRC))GetProcAddress(gl_lib, "wglMakeCurrent");
|
||||
p_wglDeleteContext = (BOOL (WINAPI *)(HGLRC))GetProcAddress(gl_lib, "wglDeleteContext");
|
||||
p_wglGetProcAddress = (PROC (WINAPI *)(LPCSTR))GetProcAddress(gl_lib, "wglGetProcAddress");
|
||||
if (!p_wglCreateContext || !p_wglMakeCurrent || !p_wglDeleteContext || !p_wglGetProcAddress) return 0;
|
||||
gl_dc = GetDC(win);
|
||||
PIXELFORMATDESCRIPTOR pfd;
|
||||
memset(&pfd, 0, sizeof pfd);
|
||||
pfd.nSize = sizeof pfd; pfd.nVersion = 1;
|
||||
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
|
||||
pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 32; pfd.iLayerType = PFD_MAIN_PLANE;
|
||||
int pf = ChoosePixelFormat(gl_dc, &pfd);
|
||||
if (!pf || !SetPixelFormat(gl_dc, pf, &pfd)) { uya_log("GL: no pixel format"); return 0; }
|
||||
HGLRC tmp = p_wglCreateContext(gl_dc);
|
||||
if (!tmp || !p_wglMakeCurrent(gl_dc, tmp)) { uya_log("GL: wglCreateContext failed"); return 0; }
|
||||
HGLRC (WINAPI *create_attribs)(HDC, HGLRC, const int *) = (HGLRC (WINAPI *)(HDC, HGLRC, const int *))plat_gl_proc("wglCreateContextAttribsARB");
|
||||
if (!create_attribs) { uya_log("GL: no wglCreateContextAttribsARB (OpenGL 3.3 needed)"); p_wglMakeCurrent(0, 0); p_wglDeleteContext(tmp); return 0; }
|
||||
const int attr[] = { 0x2091, 3, 0x2092, 3, 0x9126, 1, 0 }; /* 3.3 core */
|
||||
gl_rc = create_attribs(gl_dc, 0, attr);
|
||||
p_wglMakeCurrent(0, 0);
|
||||
p_wglDeleteContext(tmp);
|
||||
if (!gl_rc || !p_wglMakeCurrent(gl_dc, gl_rc)) { uya_log("GL: no OpenGL 3.3 core context"); return 0; }
|
||||
{ /* UYA_VSYNC=1: swaps wait for the monitor (the game paces itself at 50 Hz anyway) */
|
||||
BOOL (WINAPI *swap_interval)(int) = (BOOL (WINAPI *)(int))plat_gl_proc("wglSwapIntervalEXT");
|
||||
const char *e = getenv("UYA_VSYNC");
|
||||
if (swap_interval) swap_interval(e ? atoi(e) : 0);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
void plat_gl_current(int on) { if (p_wglMakeCurrent) p_wglMakeCurrent(on ? gl_dc : 0, on ? gl_rc : 0); }
|
||||
void plat_gl_swap(void) { SwapBuffers(gl_dc); }
|
||||
void plat_gl_size(int *w, int *h)
|
||||
{
|
||||
RECT rc;
|
||||
if (win && GetClientRect(win, &rc)) { *w = rc.right; *h = rc.bottom; }
|
||||
else { *w = 640; *h = 480; }
|
||||
}
|
||||
|
||||
int plat_present(const uint32_t *px, int w, int h)
|
||||
{
|
||||
if (win_closed) return 0;
|
||||
win_create();
|
||||
pump();
|
||||
if (win_closed) return 0;
|
||||
if (plat_gl_active) { /* the GS thread draws the window; here only the frame rate in the title */
|
||||
extern unsigned long long nsends;
|
||||
static uint64_t t0; static unsigned long long s0; static unsigned n0;
|
||||
uint64_t t = plat_time_us();
|
||||
n0++;
|
||||
if (!t0) { t0 = t; s0 = nsends; }
|
||||
else if (t - t0 >= 1000000) {
|
||||
double sec = (double)(t - t0) / 1e6;
|
||||
char title[64];
|
||||
snprintf(title, sizeof title, "Project Solana - %.0f FPS", (double)(nsends - s0) / sec);
|
||||
SetWindowTextA(win, title);
|
||||
t0 = t; s0 = nsends; n0 = 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
{
|
||||
BITMAPINFO bi = {0};
|
||||
RECT rc;
|
||||
HDC dc = GetDC(win);
|
||||
GetClientRect(win, &rc);
|
||||
bi.bmiHeader.biSize = sizeof bi.bmiHeader;
|
||||
bi.bmiHeader.biWidth = w;
|
||||
bi.bmiHeader.biHeight = -h; /* top-down */
|
||||
bi.bmiHeader.biPlanes = 1;
|
||||
bi.bmiHeader.biBitCount = 32;
|
||||
bi.bmiHeader.biCompression = BI_RGB;
|
||||
{
|
||||
/* keep 4:3, black bars around */
|
||||
int cw = rc.right, ch = rc.bottom, vw = cw, vh = cw * 3 / 4;
|
||||
if (vh > ch) { vh = ch; vw = ch * 4 / 3; }
|
||||
SetStretchBltMode(dc, COLORONCOLOR); /* HALFTONE filters nicer but costs milliseconds a frame */
|
||||
if (px) StretchDIBits(dc, (cw - vw) / 2, (ch - vh) / 2, vw, vh, 0, 0, w, h, px, &bi, DIB_RGB_COLORS, SRCCOPY);
|
||||
}
|
||||
{ /* debug: frame rate in the corner and the title (game frames drawn, and vsyncs: 50 = full speed) */
|
||||
extern unsigned long long nsends;
|
||||
static uint64_t t0; static unsigned long long s0; static unsigned n0;
|
||||
static char text[96] = "";
|
||||
uint64_t t = plat_time_us();
|
||||
n0++;
|
||||
if (!t0) { t0 = t; s0 = nsends; }
|
||||
else if (t - t0 >= 1000000) {
|
||||
double sec = (double)(t - t0) / 1e6;
|
||||
snprintf(text, sizeof text, "%.1f fps (%.1f vsyncs/s, full speed 50)", (double)(nsends - s0) / sec, n0 / sec);
|
||||
char title[64];
|
||||
snprintf(title, sizeof title, "Project Solana - %.0f FPS", (double)(nsends - s0) / sec);
|
||||
SetWindowTextA(win, title);
|
||||
t0 = t; s0 = nsends; n0 = 0;
|
||||
}
|
||||
if (text[0]) {
|
||||
SetBkMode(dc, OPAQUE); SetBkColor(dc, RGB(0, 0, 0)); SetTextColor(dc, RGB(255, 255, 0));
|
||||
TextOutA(dc, 4, 4, text, (int)strlen(text));
|
||||
}
|
||||
}
|
||||
ReleaseDC(win, dc);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* a movie starts: forget key presses from before it (the Enter that chose it) */
|
||||
/* movies: a fresh press of Cross or Start on the controller skips (buttons already held when the movie starts do not) */
|
||||
static unsigned skip_held;
|
||||
void plat_movie_started(void) { pump(); skip_held = plat_pad_buttons() & 0x4008; }
|
||||
int plat_key_skip(void)
|
||||
{
|
||||
pump();
|
||||
unsigned b = plat_pad_buttons() & 0x4008, fresh = b & ~skip_held;
|
||||
skip_held = b;
|
||||
return fresh != 0 || (getenv("UYA_SKIPMOVIES") && getenv("UYA_KEYS")); /* (testing: scripted runs skip movies) */
|
||||
}
|
||||
|
||||
void plat_poll(void)
|
||||
{
|
||||
if (!win) return;
|
||||
pump();
|
||||
if (win_closed) { uya_log("window closed"); exit(0); }
|
||||
}
|
||||
|
||||
/* ---- sound: waveOut, one buffer per call ---- */
|
||||
static HWAVEOUT wo;
|
||||
static WAVEHDR wh;
|
||||
static int16_t *wbuf;
|
||||
|
||||
void plat_audio_stop(void)
|
||||
{
|
||||
if (!wo) return;
|
||||
waveOutReset(wo);
|
||||
waveOutUnprepareHeader(wo, &wh, sizeof wh);
|
||||
waveOutClose(wo);
|
||||
wo = 0;
|
||||
free(wbuf);
|
||||
wbuf = 0;
|
||||
}
|
||||
|
||||
void plat_audio_play(const int16_t *lr, size_t frames, int rate, const char *tag)
|
||||
{
|
||||
WAVEFORMATEX fmt = {0};
|
||||
(void)tag;
|
||||
plat_audio_stop();
|
||||
fmt.wFormatTag = WAVE_FORMAT_PCM;
|
||||
fmt.nChannels = 2;
|
||||
fmt.nSamplesPerSec = (DWORD)rate;
|
||||
fmt.wBitsPerSample = 16;
|
||||
fmt.nBlockAlign = 4;
|
||||
fmt.nAvgBytesPerSec = (DWORD)rate * 4;
|
||||
if (waveOutOpen(&wo, WAVE_MAPPER, &fmt, 0, 0, CALLBACK_NULL) != MMSYSERR_NOERROR) { wo = 0; uya_log("sound: no output device"); return; }
|
||||
wbuf = malloc(frames * 4);
|
||||
if (!wbuf) { waveOutClose(wo); wo = 0; return; }
|
||||
memcpy(wbuf, lr, frames * 4);
|
||||
memset(&wh, 0, sizeof wh);
|
||||
wh.lpData = (LPSTR)wbuf;
|
||||
wh.dwBufferLength = (DWORD)(frames * 4);
|
||||
waveOutPrepareHeader(wo, &wh, sizeof wh);
|
||||
waveOutWrite(wo, &wh, sizeof wh);
|
||||
}
|
||||
|
||||
/* ---- sound effects stream: waveOut with a ring of buffers ---- */
|
||||
#define SBUFS 32
|
||||
#define SBUF_FRAMES 1024
|
||||
static HWAVEOUT so;
|
||||
static WAVEHDR sh[SBUFS];
|
||||
static int16_t sbuf[SBUFS][SBUF_FRAMES * 2];
|
||||
static int snext, sopen_failed;
|
||||
|
||||
static void stream_open(void)
|
||||
{
|
||||
WAVEFORMATEX fmt = {0};
|
||||
fmt.wFormatTag = WAVE_FORMAT_PCM;
|
||||
fmt.nChannels = 2;
|
||||
fmt.nSamplesPerSec = 48000;
|
||||
fmt.wBitsPerSample = 16;
|
||||
fmt.nBlockAlign = 4;
|
||||
fmt.nAvgBytesPerSec = 48000 * 4;
|
||||
if (waveOutOpen(&so, WAVE_MAPPER, &fmt, 0, 0, CALLBACK_NULL) != MMSYSERR_NOERROR) { so = 0; sopen_failed = 1; return; }
|
||||
for (int i = 0; i < SBUFS; i++) {
|
||||
memset(&sh[i], 0, sizeof sh[i]);
|
||||
sh[i].lpData = (LPSTR)sbuf[i];
|
||||
sh[i].dwBufferLength = SBUF_FRAMES * 4;
|
||||
waveOutPrepareHeader(so, &sh[i], sizeof sh[i]);
|
||||
sh[i].dwFlags |= WHDR_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
int plat_stream_queued(void)
|
||||
{
|
||||
int n = 0;
|
||||
if (!so) return sopen_failed ? 1 << 20 : 0;
|
||||
for (int i = 0; i < SBUFS; i++) if (!(sh[i].dwFlags & WHDR_DONE)) n += (int)(sh[i].dwBufferLength / 4);
|
||||
return n;
|
||||
}
|
||||
|
||||
void plat_stream_write(const int16_t *lr, int frames)
|
||||
{
|
||||
if (!so && !sopen_failed) stream_open();
|
||||
if (!so) return;
|
||||
while (frames > 0) {
|
||||
WAVEHDR *h = &sh[snext];
|
||||
if (!(h->dwFlags & WHDR_DONE)) return; /* ring full: drop */
|
||||
int n = frames > SBUF_FRAMES ? SBUF_FRAMES : frames;
|
||||
memcpy(sbuf[snext], lr, (size_t)n * 4);
|
||||
h->dwBufferLength = (DWORD)n * 4;
|
||||
h->dwFlags &= ~WHDR_DONE;
|
||||
waveOutWrite(so, h, sizeof *h);
|
||||
snext = (snext + 1) % SBUFS;
|
||||
lr += n * 2;
|
||||
frames -= n;
|
||||
}
|
||||
}
|
||||
|
||||
/* entry the launcher calls */
|
||||
__declspec(dllexport) int uya_main_win32(void)
|
||||
{
|
||||
int argc = 0;
|
||||
LPWSTR *wv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||
char **argv = calloc(argc + 1, sizeof *argv);
|
||||
for (int i = 0; i < argc; i++) {
|
||||
int n = WideCharToMultiByte(CP_UTF8, 0, wv[i], -1, 0, 0, 0, 0);
|
||||
argv[i] = malloc(n);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wv[i], -1, argv[i], n, 0, 0);
|
||||
}
|
||||
return uya_main(argc, argv);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,64 @@
|
||||
/* RAC1 start-up: what crt0 and main (src/core/boot.c, 0x0012DB18) do on the
|
||||
* PS2, for the host.
|
||||
*
|
||||
* main runs the executable's game program (func_001E99D8: logos, intro movie,
|
||||
* loading screen, sound bank, then the level load), then, forever, ParseBin
|
||||
* (0x0012DA38, host/src/rac1/levels.c) puts the next level's program over the
|
||||
* executable's main segment and returns its entry point, and main calls it.
|
||||
* The level programs are compiled separately (src/overlays, docs/LEVELS.md). */
|
||||
#include "uya_host.h"
|
||||
#include "uya_runtime.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void uya_gs_init(void);
|
||||
extern void func_001E99D8(void);
|
||||
void *func_0012DA38(void);
|
||||
|
||||
void rac1_boot(void)
|
||||
{
|
||||
uya_gs_init();
|
||||
uya_log("boot: GS ready");
|
||||
}
|
||||
|
||||
/* Testing: UYA_LEVEL="N@V" asks for level N at vsync V, as the title menu's
|
||||
* "go to level" (func_0022F4A0 in the executable) does, so a run can reach
|
||||
* ParseBin without the menu (only while the executable's program runs).
|
||||
* UYA_VSYNCS=N ends the run at vsync N (with the report of missing functions). */
|
||||
const void *rac1_current_level(void);
|
||||
void rac1_test_hooks(void)
|
||||
{
|
||||
static int done;
|
||||
static long stop = -2;
|
||||
if (stop == -2) { const char *s = getenv("UYA_VSYNCS"); stop = s ? atol(s) : -1; }
|
||||
if (stop > 0 && uya_frame_count() >= (uint32_t)stop) { uya_log("UYA_VSYNCS=%ld reached", stop); exit(0); }
|
||||
const char *e = getenv("UYA_LEVEL");
|
||||
if (!e || done || rac1_current_level()) return;
|
||||
int lvl = atoi(e);
|
||||
const char *at = strchr(e, '@');
|
||||
if (uya_frame_count() < (at ? (uint32_t)atoi(at + 1) : 0u)) return;
|
||||
done = 1;
|
||||
uya_log("UYA_LEVEL: asking for level %d at vsync %u", lvl, uya_frame_count());
|
||||
((void (*)(int))(uintptr_t)0x0022F4A0)(lvl);
|
||||
}
|
||||
|
||||
void rac1_run(void)
|
||||
{
|
||||
{ /* The EE's FPU and the VUs round toward zero (truncate), not to nearest. The game
|
||||
* depends on it: level 06 counts a timer down by 0.1 while it is > 0, and only a
|
||||
* truncating subtraction ends at or below zero on the step the game expects (to
|
||||
* nearest leaves a tiny positive rest, one step more makes a moby's scale negative,
|
||||
* its bounding cells cross, the grid frees cells twice and a pointer runs off RAM).
|
||||
* MXCSR RC = 11 on this thread (the game's); the GS threads exist already and keep
|
||||
* round to nearest. Denormals are left alone (VU MAX/MINI compare integer patterns). */
|
||||
unsigned m = __builtin_ia32_stmxcsr();
|
||||
__builtin_ia32_ldmxcsr((m & ~0x6000u) | 0x6000u);
|
||||
}
|
||||
uya_log("calling the executable's game program (func_001E99D8)");
|
||||
func_001E99D8();
|
||||
for (;;) {
|
||||
void (*entry)(void) = (void (*)(void))func_0012DA38();
|
||||
uya_log("main: calling the program's entry point 0x%08X", (u32)(uintptr_t)entry);
|
||||
entry();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
/* RAC1 core: the Sony libraries, newlib and kernel calls the game makes, by
|
||||
* their PAL (SCES_509.16) addresses.
|
||||
*
|
||||
* The UYA Wrapper's implementations are kept under host/src/sdk with the
|
||||
* prefix uyafn_ (their UYA addresses). Where RAC1's library code is the same
|
||||
* function (libgraph, libdma: matched instruction for instruction, see
|
||||
* docs/RAC1_NOTES.md) the RAC1 address forwards to it. The rest is written
|
||||
* here from OpenRAC's own C for these functions (src/core). */
|
||||
#include "uya_host.h"
|
||||
#include "uya_runtime.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
/* ---- newlib ------------------------------------------------------------- */
|
||||
void *func_00115248(void *d, const void *s, u32 n) { return memmove(d, s, n); } /* memcpy */
|
||||
void *func_001153FC(void *d, s32 c, u32 n) { return memset(d, c, n); } /* memset */
|
||||
s32 func_001151B4(const void *a, const void *b, u32 n) { return memcmp(a, b, n); } /* memcmp */
|
||||
char *func_00116428(const char *s, s32 c) { return strchr(s, c); } /* strchr */
|
||||
char *func_001166FC(char *d, const char *s) { return strcpy(d, s); } /* strcpy */
|
||||
u32 func_00116810(const char *s) { return (u32)strlen(s); } /* strlen */
|
||||
s32 func_001165B8(const char *a, const char *b) { return strcmp(a, b); } /* strcmp */
|
||||
s32 func_00116948(const char *a, const char *b, u32 n) { return strncmp(a, b, n); } /* strncmp */
|
||||
char *func_00116B00(char *d, const char *s, u32 n) { return strncpy(d, s, n); } /* strncpy */
|
||||
char *func_00116CC0(const char *a, const char *b) { return strstr(a, b); } /* strstr */
|
||||
|
||||
/* sprintf (0x00116248) */
|
||||
int func_00116248(char *buf, const char *fmt, ...)
|
||||
{
|
||||
va_list ap; va_start(ap, fmt);
|
||||
int n = vsprintf(buf, fmt, ap);
|
||||
va_end(ap);
|
||||
return n;
|
||||
}
|
||||
|
||||
/* srand / rand: newlib's LCG on the reent structure's seed, *(D_0012F86C) + 0x58 */
|
||||
#define RAND_SEED ((u32 *)(uintptr_t)(*(u32 *)0x0012F86C + 0x58))
|
||||
void func_001160C8(s32 seed) { *RAND_SEED = (u32)seed; }
|
||||
s32 func_001160D8(void) { return (s32)((*RAND_SEED = *RAND_SEED * 1103515245u + 12345u) & 0x7FFFFFFF); }
|
||||
|
||||
/* scePrintf (libkernl, 0x0011A6C8): to the log */
|
||||
int func_0011A6C8(const char *fmt, ...)
|
||||
{
|
||||
char b[1024];
|
||||
va_list ap; va_start(ap, fmt);
|
||||
int n = vsnprintf(b, sizeof b, fmt, ap);
|
||||
va_end(ap);
|
||||
while (n > 0 && n < (int)sizeof b && (b[n - 1] == '\n' || b[n - 1] == '\r')) b[--n] = 0;
|
||||
uya_log("game: %s", b);
|
||||
return n;
|
||||
}
|
||||
void func_00124B60(void *fmt, ...) { (void)fmt; } /* libpad2's debug print, compiled out */
|
||||
|
||||
/* ---- kernel syscalls (0x00118A40-0x00118EB0, one 16-byte stub each) ---- */
|
||||
s32 func_00118D80(s32 mode) { (void)mode; return 0; } /* FlushCache */
|
||||
s32 func_00118EA0(void) { return 0x02000000; } /* GetMemorySize */
|
||||
/* DMAC interrupt handlers: host/src/sdk/dma.c calls them as the chains run
|
||||
* (RAC1's VU1 chain handler, func_00235118, clears the chain-stage bits the
|
||||
* draw code waits on and restarts the DMA). */
|
||||
s32 uyafn_11EB30(s32 ch, s32 (*h)(s32), s32 next);
|
||||
s32 uyafn_11F9A8(s32 ch);
|
||||
s32 uya_dmac_remove(s32 ch);
|
||||
s32 uya_dmac_disable(s32 ch);
|
||||
s32 func_00118AB0(s32 ch, s32 (*h)(s32), s32 next) { return uyafn_11EB30(ch, h, next); } /* AddDmacHandler (0x12) */
|
||||
s32 func_00118AD0(s32 ch, s32 id) { (void)id; return uya_dmac_remove(ch); } /* RemoveDmacHandler (0x13) */
|
||||
s32 func_00118B00(s32 ch) { return uyafn_11F9A8(ch); } /* _EnableDmac (0x16) */
|
||||
s32 func_00118B10(s32 ch) { return uya_dmac_disable(ch); } /* _DisableDmac (0x17) */
|
||||
s32 func_00119460(s32 ch) { return uyafn_11F9A8(ch); } /* EnableDmac (libkernl, DI/EI around 0x16) */
|
||||
s32 func_001193F8(s32 ch) { return uya_dmac_disable(ch); } /* DisableDmac */
|
||||
static s32 next_sema = 1;
|
||||
s32 func_00118C70(void *param) { (void)param; return next_sema++; } /* CreateSema (0x40) */
|
||||
s32 func_00118C80(s32 id) { (void)id; return 0; } /* DeleteSema */
|
||||
s32 func_00118C90(s32 id) { (void)id; return 0; } /* SignalSema */
|
||||
s32 func_00118CB0(s32 id) { return id; } /* WaitSema */
|
||||
s32 func_00118CC0(s32 id) { return id; } /* PollSema */
|
||||
s32 func_00118BE0(void) { return 1; } /* GetThreadId (0x2F) */
|
||||
s32 func_00118E10(s32 id) { (void)id; return -1; } /* sceSifDmaStat (0x76): done */
|
||||
/* IOP memory, as far as the game's own data goes there: sceSifSetDma's EE ->
|
||||
* IOP transfers land here, and the stash server (IOPSTASH.IRX, below) hands
|
||||
* it back */
|
||||
#define IOP_RAM_SIZE (2u << 20)
|
||||
static u8 iop_ram[IOP_RAM_SIZE] __attribute__((aligned(16)));
|
||||
s32 func_00118E20(void *dma, s32 n) /* sceSifSetDma (0x77): {src, dest, size, attr} x n */
|
||||
{
|
||||
const u32 *d = (const u32 *)dma;
|
||||
for (s32 i = 0; d && i < n; i++, d += 4) {
|
||||
u32 src = d[0], dst = d[1] & 0x1FFFFFu, size = d[2];
|
||||
if ((src >> 28) == 2 || (src >> 28) == 3) src &= 0x0FFFFFFFu; /* uncached views */
|
||||
if (dst < IOP_RAM_SIZE && size <= IOP_RAM_SIZE - dst && src >= PS2_RAM_LO && src + size <= PS2_RAM_HI)
|
||||
memcpy(iop_ram + dst, (const void *)(uintptr_t)src, size);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
s32 func_00118E70(s32 reg) { (void)reg; return 0x00070000; } /* sceSifGetReg (0x7A): IOP booted, SIF ready */
|
||||
|
||||
/* ---- SIF RPC (libkernl): no IOP. A call completes at once with a zeroed
|
||||
* reply, which the game's wrappers read as "nothing to report", except for
|
||||
* the servers the host provides:
|
||||
* - 0x11, IOPSTASH.IRX (the game's "stash", src/game/stash.c): a buffer of
|
||||
* 0x94000 bytes in IOP memory (the module's stash_ram) where the game keeps
|
||||
* data it fetches back later (the pause menu's weapon models and
|
||||
* animations). RPC 2 answers {base, size}; RPC 1 gets {IOP address} and
|
||||
* sends back the receive size's worth of IOP memory from there (the
|
||||
* module's rpc_stash_mgr returns that address as its reply buffer). The
|
||||
* game sends the data with sceSifSetDma (above). Without it every stash
|
||||
* slot was refused (size 0) and the pause menu unpacked the weapon data
|
||||
* from a scrubbed buffer (a crash reading 0xDEE6BEEF). ---------- */
|
||||
#define STASH_BASE 0x00160000u
|
||||
#define STASH_SIZE 0x00094000u
|
||||
void func_0011AE20(s32 mode) { (void)mode; } /* sceSifInitRpc */
|
||||
s32 func_0011B2F8(void *cd, u32 server, s32 mode) /* sceSifBindRpc */
|
||||
{
|
||||
(void)mode;
|
||||
if (cd) { ((u32 *)cd)[9] = 0x00F00000 | (server & 0xFFFF); } /* a nonzero server pointer */
|
||||
return 0;
|
||||
}
|
||||
s32 func_0011B4C8(void *cd, s32 fno, s32 mode, void *send, s32 ssize, void *recv, s32 rsize,
|
||||
void (*end)(void *), void *endparam) /* sceSifCallRpc */
|
||||
{
|
||||
(void)mode; (void)ssize;
|
||||
u32 server = cd ? ((u32 *)cd)[9] & 0xFFFF : 0;
|
||||
if (recv && rsize > 0) memset(recv, 0, (size_t)rsize);
|
||||
if (server == 0x11 && recv) { /* IOPSTASH */
|
||||
if (fno == 2 && rsize >= 8) { ((u32 *)recv)[0] = STASH_BASE; ((u32 *)recv)[1] = STASH_SIZE; }
|
||||
else if (fno == 1 && send) {
|
||||
u32 src = ((const u32 *)send)[0] & 0x1FFFFFu;
|
||||
if (rsize > 0 && src < IOP_RAM_SIZE && (u32)rsize <= IOP_RAM_SIZE - src) memcpy(recv, iop_ram + src, (size_t)rsize);
|
||||
}
|
||||
}
|
||||
if (end) end(endparam);
|
||||
return 0;
|
||||
}
|
||||
s32 func_0011B6B8(void *cd) { (void)cd; return 0; } /* sceSifCheckStatRpc: idle */
|
||||
|
||||
/* ---- libgraph and libdma: the UYA implementations ------------------------ */
|
||||
void uyafn_12BEE8(s32 mode, s32 inter, s32 omode, s32 ffmode);
|
||||
void uyafn_12C138(u64 *disp, s32 psm, s32 w, s32 h, s32 dx, s32 dy);
|
||||
void uyafn_12C4B0(u64 *disp);
|
||||
s16 uyafn_12C570(s16 psm, s16 w, s16 h);
|
||||
s32 uyafn_12C638(u64 *d, s16 psm, s16 w, s16 h, s16 ztest, s16 zpsm);
|
||||
s32 uyafn_12C908(s32 mode);
|
||||
s32 uyafn_12C9A0(u64 *lp, s32 dbp, s32 dbw, s32 dpsm, s32 x, s32 y, s32 w, s32 h);
|
||||
s32 uyafn_12CCC8(u64 *lp, void *src);
|
||||
s32 uyafn_12D4D8(s32 fn);
|
||||
void *uyafn_12D650(s32 id);
|
||||
s32 uyafn_12D938(void *chan, void *tag);
|
||||
void uyafn_12A950(void);
|
||||
void *uyafn_12C078(void);
|
||||
void uya_gs_gif(const u32 *d, u32 qwc);
|
||||
|
||||
void func_00121B78(s16 mode, s16 inter, s16 out, s16 ff) { uyafn_12BEE8(mode, inter, out, ff); } /* sceGsResetGraph */
|
||||
void *func_00121D08(void) { return uyafn_12C078(); } /* sceGsGetGParam */
|
||||
void func_00121DC8(u64 *d, s32 psm, s32 w, s32 h, s32 dx, s32 dy) { uyafn_12C138(d, psm, w, h, dx, dy); } /* sceGsSetDefDispEnv */
|
||||
void func_00122140(u64 *d) { uyafn_12C4B0(d); } /* sceGsPutDispEnv */
|
||||
s16 func_00122200(s16 psm, s16 w, s16 h) { return uyafn_12C570(psm, w, h); }
|
||||
s32 func_001222C8(u64 *d, s16 psm, s16 w, s16 h, s16 zt, s16 zpsm) { return uyafn_12C638(d, psm, w, h, zt, zpsm); } /* sceGsSetDefDrawEnv */
|
||||
s32 func_00122598(s32 mode) { return uyafn_12C908(mode); } /* sceGsSyncV */
|
||||
/* testing: UYA_SICHECK=v also logs sceGsSetDefLoadImage calls from vsync v */
|
||||
s32 func_00122630(u64 *lp, s32 dbp, s32 dbw, s32 dpsm, s32 x, s32 y, s32 w, s32 h) /* sceGsSetDefLoadImage */
|
||||
{
|
||||
static int n; static long v0 = -2;
|
||||
if (v0 == -2) { const char *e = getenv("UYA_SICHECK"); v0 = e ? atol(e) : -1; }
|
||||
if (v0 >= 0 && (long)uya_frame_count() >= v0 && n++ < 400)
|
||||
uya_log("SetDefLoadImage v%u: lp %08X bp %X bw %d psm %X (%d,%d) %dx%d", uya_frame_count(), (u32)(uintptr_t)lp, dbp, dbw, dpsm, x, y, w, h);
|
||||
return uyafn_12C9A0(lp, dbp, dbw, dpsm, x, y, w, h);
|
||||
}
|
||||
s32 func_00122958(u64 *lp, void *src) { return uyafn_12CCC8(lp, src); } /* sceGsExecLoadImage */
|
||||
/* sceGsSetDefStoreImage (0x00122818) / sceGsExecStoreImage (0x00122AD8): GS -> memory.
|
||||
* The pause menu saves VRAM strips with them before it draws over that memory. */
|
||||
void uya_gs_store_image(u32 bp, u32 bw, u32 psm, u32 x, u32 y, u32 w, u32 h, void *dst);
|
||||
s32 func_00122818(s32 *sp, s32 sbp, s32 sbw, s32 spsm, s32 x, s32 y, s32 w, s32 h)
|
||||
{
|
||||
sp[0] = 0x53544F52; sp[1] = sbp; sp[2] = sbw; sp[3] = spsm; sp[4] = x; sp[5] = y; sp[6] = w; sp[7] = h;
|
||||
return 0;
|
||||
}
|
||||
s32 func_00122AD8(s32 *sp, void *dst)
|
||||
{
|
||||
if (sp[0] != 0x53544F52) return -1;
|
||||
{ static int n; if (getenv("UYA_SICHECK") && n++ < 40) uya_log("StoreImage: bp %X bw %d psm %X (%d,%d) %dx%d to %08X", sp[1], sp[2], sp[3], sp[4], sp[5], sp[6], sp[7], (u32)(uintptr_t)dst); }
|
||||
{ /* PS2 uncached / accelerated mirrors of main RAM */
|
||||
u32 a = (u32)(uintptr_t)dst;
|
||||
if (a >= 0x20000000 && a < 0x40000000) dst = (void *)(uintptr_t)(a & 0x0FFFFFFF);
|
||||
}
|
||||
uya_gs_store_image((u32)sp[1], (u32)sp[2], (u32)sp[3], (u32)sp[4], (u32)sp[5], (u32)sp[6], (u32)sp[7], dst);
|
||||
return 0;
|
||||
}
|
||||
s32 func_00123168(s32 fn) { return uyafn_12D4D8(fn); } /* sceGsSyncVCallback */
|
||||
void *func_001232E0(s32 id) { return uyafn_12D650(id); } /* sceDmaGetChan */
|
||||
s32 func_001235C8(void *chan, void *tag) { return uyafn_12D938(chan, tag); } /* sceDmaSend */
|
||||
void func_001207B8(void) { uyafn_12A950(); } /* sceGsResetPath */
|
||||
s32 func_00123308(s32 mode) { (void)mode; return 0; } /* sceDmaReset */
|
||||
|
||||
/* sceGsPutDrawEnv (0x001224B0): a GIF packet (GIFtag + A+D pairs) over channel 2 */
|
||||
s32 func_001224B0(void *pkt)
|
||||
{
|
||||
u32 *q = (u32 *)pkt;
|
||||
u32 nloop = q[0] & 0x7FFF;
|
||||
{ extern const char *uya_gs_src; uya_gs_src = "PATH3 (GS library)"; }
|
||||
uya_gs_gif(q, nloop + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- libcdvd ------------------------------------------------------------ */
|
||||
/* sceCdCallback (0x00120978): the handler runs when a read completes. Reads
|
||||
* finish at once here, so it is owed and delivered at the next sceCdSync or
|
||||
* vsync, after the caller has recorded the request (RAC1's stream code sets
|
||||
* its "busy" flag right after starting the read; the callback clears it). */
|
||||
static void (*cd_cb)(s32);
|
||||
static int cd_cb_owed;
|
||||
void *func_00120978(void (*cb)(s32)) { void *old = (void *)cd_cb; cd_cb = cb; return old; }
|
||||
void rac1_cd_service(void)
|
||||
{
|
||||
if (cd_cb_owed && cd_cb) { cd_cb_owed = 0; cd_cb(1); }
|
||||
}
|
||||
s32 func_00121750(s32 lsn, s32 n, void *buf, void *mode) /* sceCdRead: done at once */
|
||||
{
|
||||
cd_cb_owed = 1;
|
||||
(void)mode;
|
||||
static int log = -1;
|
||||
if (log < 0) log = getenv("UYA_DISCLOG") != 0;
|
||||
if (log) uya_log("disc read LSN %d x%d -> 0x%08X", lsn, n, (u32)(uintptr_t)buf);
|
||||
if (uya_disc_read((u32)lsn, (u32)n, buf)) uya_log("disc read failed at LSN %d", lsn);
|
||||
return 1;
|
||||
}
|
||||
s32 func_00120F30(s32 mode) { (void)mode; rac1_cd_service(); return 0; } /* sceCdSync: idle */
|
||||
s32 func_00121930(void) { return 0; } /* sceCdGetError: none */
|
||||
s32 func_001219C8(void) { return 1; } /* sceCdBreak */
|
||||
s32 func_00120D28(s32 x) { (void)x; return 1; } /* cd_check_ncmd: ready */
|
||||
s32 func_00121040(s32 x) { (void)x; return 1; } /* cd_check_scmd: ready */
|
||||
s32 func_00121A80(u8 *clk) /* sceCdReadClock: BCD */
|
||||
{
|
||||
time_t t = time(0); struct tm *m = localtime(&t);
|
||||
#define BCD(v) (u8)((((v) / 10) << 4) | ((v) % 10))
|
||||
if (clk) { clk[0] = 0; clk[1] = BCD(m->tm_sec); clk[2] = BCD(m->tm_min); clk[3] = BCD(m->tm_hour);
|
||||
clk[4] = 0; clk[5] = BCD(m->tm_mday); clk[6] = BCD(m->tm_mon + 1); clk[7] = BCD(m->tm_year % 100); }
|
||||
#undef BCD
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ---- IOP start-up (InitOnce, func_00201E88): there is no IOP; every step succeeds ---- */
|
||||
s32 func_0011D248(char *img) { uya_log("IOP reboot with %s (no IOP on the host)", img); return 1; } /* sceSifRebootIop */
|
||||
s32 func_0011D210(void) { return 1; } /* sceSifSyncIop */
|
||||
s32 func_0011D960(void) { return 0; } /* sceSifInitIopHeap */
|
||||
void func_0011D9A8(void) {}
|
||||
s32 func_0011CB40(void) { return 0; }
|
||||
s32 func_0011BF48(void) { return 0; } /* sceFsReset */
|
||||
s32 func_001211B0(s32 mode) { (void)mode; return 1; } /* sceCdInit */
|
||||
s32 func_00121490(s32 mode) { (void)mode; return 1; } /* sceCdMmode / sync */
|
||||
s32 func_00121688(s32 mode) { (void)mode; return 2; } /* sceCdDiskReady: ready */
|
||||
s32 func_00118D60(s32 mode) { (void)mode; return 0; } /* syscall 0x61 */
|
||||
|
||||
/* ---- game helpers in hand-written assembly ---- */
|
||||
void uya_dma_poll(void);
|
||||
/* 0x001F9988: a short busy wait (the game's DMA wait loops call it while a
|
||||
* channel's STR bit is set); here it runs the pending transfers. */
|
||||
void func_001F9988(s32 n) { (void)n; uya_dma_poll(); }
|
||||
|
||||
/* printf (newlib, 0x00116078): to the log */
|
||||
int func_00116078(const char *fmt, ...)
|
||||
{
|
||||
char b[1024];
|
||||
va_list ap; va_start(ap, fmt);
|
||||
int n = vsnprintf(b, sizeof b, fmt, ap);
|
||||
va_end(ap);
|
||||
while (n > 0 && n < (int)sizeof b && (b[n - 1] == '\n' || b[n - 1] == '\r')) b[--n] = 0;
|
||||
uya_log("game: %s", b);
|
||||
return n;
|
||||
}
|
||||
|
||||
/* ---- libmc: the UYA Wrapper's folder-backed card (host/src/sdk/memcard.c) ---- */
|
||||
s32 uyafn_12E2E0(s32 mode, s32 *cmd, s32 *result);
|
||||
s32 uyafn_12E400(s32 port, s32 slot, s32 *type, s32 *free_kb, s32 *format);
|
||||
s32 uyafn_12E580(s32 port, s32 slot, char *name, s32 mode, s32 maxent, void *table);
|
||||
s32 uyafn_12DCA0(s32 port, s32 slot, char *name, s32 mode);
|
||||
s32 uyafn_12DDC8(s32 port, s32 slot, char *name);
|
||||
s32 uyafn_12DE00(s32 fd);
|
||||
s32 uyafn_12DEC0(s32 fd, s32 off, s32 whence);
|
||||
s32 uyafn_12E050(s32 fd, void *buf, s32 size);
|
||||
s32 uyafn_12E168(s32 fd, void *buf, s32 size);
|
||||
s32 uyafn_12E7A8(s32 port, s32 slot, char *name);
|
||||
s32 uyafn_12E6D8(s32 port, s32 slot);
|
||||
s32 uyafn_12E8D8(s32 port, s32 slot);
|
||||
s32 func_001236F0(void) { return 0; } /* sceMcInit */
|
||||
s32 func_001238B0(s32 port, s32 slot, char *name, s32 mode) { return uyafn_12DCA0(port, slot, name, mode); } /* sceMcOpen */
|
||||
s32 func_001239D8(s32 port, s32 slot, char *name) { return uyafn_12DDC8(port, slot, name); } /* sceMcMkdir */
|
||||
s32 func_00123A10(s32 fd) { return uyafn_12DE00(fd); } /* sceMcClose */
|
||||
s32 func_00123AC8(s32 fd, s32 off, s32 whence) { return uyafn_12DEC0(fd, off, whence); } /* sceMcSeek */
|
||||
s32 func_00123C30(s32 fd, void *buf, s32 n) { return uyafn_12E050(fd, buf, n); } /* sceMcRead */
|
||||
s32 func_00123D48(s32 fd, void *buf, s32 n) { return uyafn_12E168(fd, buf, n); } /* sceMcWrite */
|
||||
s32 func_00123F30(s32 mode, s32 *cmd, s32 *result) { return uyafn_12E2E0(mode, cmd, result); } /* sceMcSync */
|
||||
s32 func_00124068(s32 port, s32 slot, s32 *type, s32 *free_kb, s32 *format) { return uyafn_12E400(port, slot, type, free_kb, format); } /* sceMcGetInfo */
|
||||
s32 func_001241F0(s32 port, s32 slot, char *name, s32 mode, s32 n, void *table) { return uyafn_12E580(port, slot, name, mode, n, table); } /* sceMcGetDir */
|
||||
s32 func_00124340(s32 port, s32 slot) { return uyafn_12E6D8(port, slot); } /* sceMcFormat (RPC 0x10) */
|
||||
s32 func_00124410(s32 port, s32 slot, char *name) { return uyafn_12E7A8(port, slot, name); } /* sceMcDelete */
|
||||
s32 func_00124528(s32 port, s32 slot) { return uyafn_12E8D8(port, slot); } /* sceMcUnformat (RPC 0x11) */
|
||||
s32 func_00124650(void) { return 0; } /* bind the mc/pad IOP servers */
|
||||
|
||||
/* ---- libscf ---- */
|
||||
s32 func_0012D380(void) { return 1; } /* system language: 1 = English (InitOnce maps it to the game's language 0) */
|
||||
void func_0012D818(u8 *clk) { (void)clk; } /* sceScfGetLocalTimefromRTC: sceCdReadClock (func_00121A80) already gives local time */
|
||||
|
||||
/* ---- libpad2 ---- */
|
||||
unsigned plat_pad_buttons(void);
|
||||
int plat_pad_sticks(u8 st[4]); /* left X, left Y, right X, right Y (0x7F centre); 0: no gamepad */
|
||||
s32 func_00124B88(void) { return 1; } /* scePad2Init */
|
||||
s32 func_00124BC8(void *param, char *buf) { (void)param; (void)buf; return 0; } /* scePad2CreateSocket: socket 0 */
|
||||
s32 func_00124EE0(s32 port) { (void)port; return 1; } /* scePad2GetState: 1 = ready (UpdatePad, pad.c) */
|
||||
s32 func_00124DF0(s32 port, u8 *buf) { (void)port; if (buf) memset(buf, 0, 4); return 0; } /* button profile: none */
|
||||
s32 func_00125218(s32 port, u8 *buf) { (void)port; if (buf) memset(buf, 0, 4); return 0; }
|
||||
/* scePad2Read(socket, buf): buf[0] = Select..Left, buf[1] = L2..Square (both inverted), then the
|
||||
* four sticks (0x7F = centre) and twelve pressures, as UpdatePad's report parser (func_002181F0) reads them. */
|
||||
s32 func_00124D18(s32 id, u8 *buf)
|
||||
{
|
||||
(void)id;
|
||||
unsigned b = id == 0 ? plat_pad_buttons() : 0;
|
||||
buf[0] = (u8)~(b & 0xFF);
|
||||
buf[1] = (u8)~(b >> 8);
|
||||
for (int i = 2; i < 6; i++) buf[i] = 0x7F;
|
||||
/* bits 16..19 (UYA_KEYS lup/ldown/lleft/lright): the left stick, buf[4] X and buf[5] Y */
|
||||
if (b & 0x40000) buf[4] = 0x00; else if (b & 0x80000) buf[4] = 0xFF;
|
||||
if (b & 0x10000) buf[5] = 0x00; else if (b & 0x20000) buf[5] = 0xFF;
|
||||
/* bits 20..23: the right stick, buf[2] X and buf[3] Y */
|
||||
if (b & 0x400000) buf[2] = 0x00; else if (b & 0x800000) buf[2] = 0xFF;
|
||||
if (b & 0x100000) buf[3] = 0x00; else if (b & 0x200000) buf[3] = 0xFF;
|
||||
{ /* a gamepad's sticks (Windows: XInput), where it has one and they are off centre */
|
||||
u8 st[4];
|
||||
if (id == 0 && plat_pad_sticks(st)) {
|
||||
if (st[0] != 0x7F) buf[4] = st[0];
|
||||
if (st[1] != 0x7F) buf[5] = st[1];
|
||||
if (st[2] != 0x7F) buf[2] = st[2];
|
||||
if (st[3] != 0x7F) buf[3] = st[3];
|
||||
}
|
||||
}
|
||||
/* pressures: right, left, up, down, triangle, circle, cross, square, L1, R1, L2, R2 */
|
||||
static const int bit[12] = { 5, 7, 4, 6, 12, 13, 14, 15, 10, 11, 8, 9 };
|
||||
for (int i = 0; i < 12; i++) buf[6 + i] = (b >> bit[i]) & 1 ? 0xFF : 0;
|
||||
{ /* testing: UYA_PADLOG=1 logs the report whenever it changes */
|
||||
static int on = -1; static u8 last[6];
|
||||
if (on < 0) on = getenv("UYA_PADLOG") != 0;
|
||||
static int hero = -1;
|
||||
if (hero < 0) hero = getenv("UYA_HEROLOG") != 0;
|
||||
if (hero && id == 0) { /* testing: the hero's position, yaw, velocity and state (D_0013F450) */
|
||||
const float *m = (const float *)(uintptr_t)0x0013F4D0;
|
||||
const u8 *pd = (const u8 *)(uintptr_t)0x0013CA40;
|
||||
int ix = (*(const s16 *)(pd + 0x18E) + 29) % 30;
|
||||
uya_log("v%u hero: pos %.2f %.2f %.2f yaw %.3f vel %.3f %.3f %.3f state %d | pad axes %.2f %.2f %.2f %.2f held %X ang %.3f mag %.3f", uya_frame_count(), m[0], m[1], m[2], m[6], m[24], m[25], m[26], *(const s32 *)(uintptr_t)(0x0013F450 + 0x198),
|
||||
((const float *)(pd + 0x100))[0], ((const float *)(pd + 0x100))[1], ((const float *)(pd + 0x100))[2], ((const float *)(pd + 0x100))[3], *(const s32 *)(pd + 0x1A0),
|
||||
((const float *)(pd + 0x258))[ix], ((const float *)(pd + 0x2D0))[ix]);
|
||||
const float *g = (const float *)(uintptr_t)0x0015EE60;
|
||||
const u8 *h = (const u8 *)(uintptr_t)0x0013F450;
|
||||
uya_log(" scales %g %g %g %g %g | hstate %d f180 %.3f f190 %.4f f194 %.4f f414 %.3f f45C %.3f i208C %d", g[0], g[1], g[2], g[3], g[4],
|
||||
*(const s32 *)(h + 0x2084), *(const float *)(h + 0x180), *(const float *)(h + 0x190), *(const float *)(h + 0x194), *(const float *)(h + 0x414), *(const float *)(h + 0x45C), *(const s32 *)(h + 0x208C));
|
||||
}
|
||||
if (on && id == 0 && memcmp(last, buf, 6)) { memcpy(last, buf, 6); uya_log("pad: buttons %02X%02X RX %02X RY %02X LX %02X LY %02X", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]); }
|
||||
}
|
||||
return 18;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/* Level programs (docs/LEVELS.md).
|
||||
*
|
||||
* ParseBin (0x0012DA38, src/core/boot.c) is the PS2's loader for the next
|
||||
* program: by the time main calls it, the game has read the level's code
|
||||
* overlay from the disc into the buffer D_0015EF4C points at (its first word
|
||||
* is the overlay's offset). ParseBin copies the overlay's records (lit, bss,
|
||||
* data, the three vtables, text: (address, size, type, entry) and the bytes)
|
||||
* over the executable's main segment while their entry point agrees, and
|
||||
* returns it. The host does the same copy, so every data record lands where
|
||||
* the PS2 puts it, and then switches the code: the level's text becomes int3
|
||||
* (like all PS2 code space), the level's trampolines go in at its function
|
||||
* addresses (its C from src/overlays, its translated functions), and the
|
||||
* recompiled code's dispatch table becomes the level's. The entry point it
|
||||
* returns is then a trampoline into the level's own code. */
|
||||
#include "uya_host.h"
|
||||
#include "uya_runtime.h"
|
||||
#include "rac1_levels.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define D_0015EF4C (*(u32 *)0x0015EF4C) /* core_lit: the level buffer */
|
||||
#define EXE_TEXT_LO 0x001E9080u /* the executable's game program */
|
||||
#define EXE_TEXT_HI 0x0023E740u
|
||||
|
||||
struct chunk { u32 dst, size, pad8, id; };
|
||||
|
||||
static const rac1_level_t *current;
|
||||
const rac1_level_t *rac1_current_level(void) { return current; }
|
||||
|
||||
/* int3 over [lo, hi) except where a record of the program just copied lies */
|
||||
static void trap_fill_outside(u32 lo, u32 hi, const struct chunk *const *recs, int n)
|
||||
{
|
||||
while (lo < hi) {
|
||||
u32 end = hi;
|
||||
int inside = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
u32 a = recs[i]->dst, b = recs[i]->dst + recs[i]->size;
|
||||
if (a <= lo && lo < b) { lo = b; inside = 1; break; }
|
||||
if (lo < a && a < end) end = a;
|
||||
}
|
||||
if (inside) continue;
|
||||
uya_fill_code_traps(lo, end);
|
||||
lo = end;
|
||||
}
|
||||
}
|
||||
|
||||
/* ParseBin (0x0012DA38) */
|
||||
void *func_0012DA38(void)
|
||||
{
|
||||
u8 *p = (u8 *)(uintptr_t)(D_0015EF4C + *(u32 *)(uintptr_t)D_0015EF4C);
|
||||
const struct chunk *recs[16];
|
||||
int n = 0;
|
||||
u32 id = 0, text_addr = 0, text_bytes = 0;
|
||||
for (;;) {
|
||||
const struct chunk *c = (const struct chunk *)p;
|
||||
if (id == 0) id = c->id;
|
||||
else if (c->id != id) break;
|
||||
if (c->dst < PS2_RAM_LO || c->dst + c->size > PS2_RAM_HI || n == 16) {
|
||||
uya_log("ParseBin: record %d at 0x%08X (+0x%X) is outside RAM; stopping", n, c->dst, c->size);
|
||||
exit(1);
|
||||
}
|
||||
memcpy((void *)(uintptr_t)c->dst, c + 1, c->size);
|
||||
uya_log("ParseBin: record %d: 0x%08X-0x%08X (type %u)", n, c->dst, c->dst + c->size, c->pad8);
|
||||
if (c->dst <= id && id < c->dst + c->size) { text_addr = c->dst; text_bytes = c->size; }
|
||||
recs[n++] = c;
|
||||
p += sizeof *c + c->size;
|
||||
}
|
||||
const rac1_level_t *lv = 0;
|
||||
for (int i = 0; i < rac1_level_count; i++)
|
||||
if (rac1_levels[i].entry == id && rac1_levels[i].text_addr == text_addr && rac1_levels[i].text_bytes == text_bytes)
|
||||
lv = &rac1_levels[i];
|
||||
if (!lv) {
|
||||
uya_log("ParseBin: a program with entry 0x%08X and text 0x%08X (+0x%X) was loaded; no level program built into the "
|
||||
"wrapper matches it (build.py --levels)", id, text_addr, text_bytes);
|
||||
exit(0);
|
||||
}
|
||||
/* code: the executable's game program and the level's text are PS2 code
|
||||
* space no more and not yet (int3), then the level's trampolines */
|
||||
trap_fill_outside(EXE_TEXT_LO, EXE_TEXT_HI, recs, n);
|
||||
uya_fill_code_traps(text_addr, text_addr + text_bytes);
|
||||
uya_install_tramp_table(lv->tramps, *lv->tramp_count);
|
||||
rc_set_table(lv->rc_table, *lv->rc_count);
|
||||
current = lv;
|
||||
uya_log("ParseBin: level %02d's program is in (%d records), entry 0x%08X (vsync %u)", lv->id, n, id, uya_frame_count());
|
||||
return (void *)(uintptr_t)id;
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/* Runtime for the recompiled assembly-only functions (tools/recomp.py,
|
||||
* host/include/rc.h): VU0 state, the stack the translated code runs on,
|
||||
* calls by address and the few instructions that reach the host. */
|
||||
#include "rc.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
void uya_log(const char *fmt, ...);
|
||||
void uya_unimpl(const char *name);
|
||||
uint32_t uya_frame_count(void);
|
||||
|
||||
RcVu0 rc_vu0 = { .vf = { [0] = {0, 0, 0, 1} } };
|
||||
|
||||
/* the translated code's stack: 1 MB of host memory (DMA from it works, see sdk/dma.c) */
|
||||
static u8 rc_stack[1 << 20] __attribute__((aligned(64)));
|
||||
u32 rc_sp = 0;
|
||||
|
||||
/* the x86 stack (see RC_QA in rc.h): from a little above the first call into
|
||||
* translated code down 8 MB, never overlapping PS2 memory (if the stack lies
|
||||
* below 32 MB, as it can on Windows, the exception is off) */
|
||||
u32 rc_cstack_lo, rc_cstack_hi;
|
||||
|
||||
#include <stdlib.h>
|
||||
/* testing: UYA_NANWATCH=addr (hex) stops the run (SIGILL, for a debugger) at the
|
||||
* first entry into or dispatch from translated code that finds the float at addr
|
||||
* NaN or infinite, and logs where the check before and this one were made */
|
||||
static void nan_check(const char *where, u32 a)
|
||||
{
|
||||
static u32 w = 1; static void *prev; static u32 preva;
|
||||
if (w == 1) { const char *e = getenv("UYA_NANWATCH"); w = e ? (u32)strtoul(e, 0, 16) : 0; }
|
||||
if (!w) return;
|
||||
u32 v = *(u32 *)(uintptr_t)w;
|
||||
if ((v & 0x7F800000u) == 0x7F800000u) { uya_log("NANWATCH: %08X became %08X; previous check %p/%08X, now %s %p/%08X", w, v, prev, preva, where, __builtin_return_address(1), a); __builtin_trap(); }
|
||||
prev = __builtin_return_address(1); preva = a;
|
||||
}
|
||||
void rc_enter(RcCtx *c)
|
||||
{
|
||||
nan_check("enter", 0);
|
||||
if (!rc_cstack_hi) {
|
||||
u32 here = (u32)(uintptr_t)__builtin_frame_address(0);
|
||||
/* the x86 stack: above PS2 RAM (0-0x01FFFFFF), which Windows' main
|
||||
* thread stack often is just above (0x03xxxxxx), Linux's far above */
|
||||
if (here > 0x02100000u) {
|
||||
rc_cstack_hi = here < 0xFFF00000u ? here + (1u << 20) : 0xFFFFFFFFu;
|
||||
rc_cstack_lo = here - (8u << 20);
|
||||
if (rc_cstack_lo < 0x02000000u) rc_cstack_lo = 0x02000000u;
|
||||
} else rc_cstack_hi = 1; /* checked: empty range */
|
||||
uya_log("x86 stack for translated code: %08X-%08X", rc_cstack_lo, rc_cstack_hi);
|
||||
}
|
||||
memset(c, 0, sizeof *c);
|
||||
if (!rc_sp) rc_sp = (u32)(uintptr_t)(rc_stack + sizeof rc_stack - 64);
|
||||
c->r[29].d[0] = (rc_sp - 64) & ~15u;
|
||||
c->r[28].d[0] = RC_GP;
|
||||
}
|
||||
void rc_leave(RcCtx *c) { (void)c; }
|
||||
|
||||
typedef void (*rc_fn)(RcCtx *);
|
||||
typedef struct { u32 addr; rc_fn fn; } rc_row;
|
||||
extern const rc_row rc_table[];
|
||||
extern const int rc_table_count;
|
||||
void uya_trap_call(uint32_t ps2_addr, uint32_t ret_addr, const uint32_t *args);
|
||||
|
||||
/* The table in use: the executable's (rc_table) until ParseBin loads a level
|
||||
* (rcNN_table, host/src/rac1/levels.c). A level's table has its own
|
||||
* functions and thunks for the core functions it calls. */
|
||||
static const rc_row *cur_table = rc_table;
|
||||
static int cur_count = -1;
|
||||
void rc_set_table(const rc_row *table, int count) { cur_table = table; cur_count = count; }
|
||||
|
||||
void rc_dispatch(RcCtx *c, u32 addr)
|
||||
{
|
||||
nan_check("dispatch", addr);
|
||||
if (cur_count < 0) cur_count = rc_table_count;
|
||||
int lo = 0, hi = cur_count - 1;
|
||||
while (lo <= hi) {
|
||||
int m = (lo + hi) / 2;
|
||||
if (cur_table[m].addr == addr) { cur_table[m].fn(c); return; }
|
||||
if (cur_table[m].addr < addr) lo = m + 1; else hi = m - 1;
|
||||
}
|
||||
u32 args[4] = { (u32)c->r[4].d[0], (u32)c->r[5].d[0], (u32)c->r[6].d[0], (u32)c->r[7].d[0] };
|
||||
uya_trap_call(addr, (u32)c->r[31].d[0], args);
|
||||
c->r[2].d[0] = 0;
|
||||
}
|
||||
|
||||
/* rc_vcallms: host/src/gfx/vu.c (translated VU0 microprograms) */
|
||||
|
||||
void rc_syscall(RcCtx *c) { uya_log("recompiled code: syscall %d", (int)(s32)c->r[3].d[0]); }
|
||||
void rc_break(u32 pc) { uya_log("recompiled code: break/trap at 0x%08X", pc); }
|
||||
void rc_bad(u32 pc, const char *what)
|
||||
{
|
||||
static int n;
|
||||
if (n++ < 64) uya_log("recompiled code: instruction %s at 0x%08X is not supported", what, pc);
|
||||
}
|
||||
u32 rc_cop0(int r)
|
||||
{
|
||||
uint32_t uya_frame_us(void);
|
||||
if (r == 9) return (u32)((unsigned long long)uya_frame_count() * uya_frame_us() * 294912u / 1000u); /* Count: 294.912 MHz, one field per vsync (20 ms PAL) */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* testing: UYA_QUADCHECK=1 logs drawquad (func_001F7EF8) calls whose quad's
|
||||
* GS words (+112: TEX0 at +120, TEX1 +128, ALPHA +136) are not what its
|
||||
* callers put there, with the caller's return address */
|
||||
void uya_dbg_quad(RcCtx *c)
|
||||
{
|
||||
static int on = -1, left = 30;
|
||||
if (on < 0) on = getenv("UYA_QUADCHECK") != 0;
|
||||
if (!on || left <= 0) return;
|
||||
u32 a = (u32)c->r[4].d[0];
|
||||
const u32 *q = (const u32 *)RC_QA(a + 112);
|
||||
if (q[4] == 0x00000260 && q[5] == 0xFF90 && q[6] == 0x44 && q[7] == 0x80) return;
|
||||
left--;
|
||||
uya_log("quadcheck: drawquad(%08X) from ra %08X: GS words %08X%08X %08X%08X %08X%08X %08X%08X", a, (u32)c->r[31].d[0],
|
||||
q[1], q[0], q[3], q[2], q[5], q[4], q[7], q[6]);
|
||||
}
|
||||
|
||||
/* testing: functions named in UYA_RC_TRACE at build time call this on entry */
|
||||
void rc_trace(RcCtx *c, const char *name)
|
||||
{
|
||||
/* UYA_RC_TRACE_MEM=addr,len[;addr,len...] (hex): memory shown with each call */
|
||||
static char mem[256] = "?";
|
||||
if (mem[0] == '?') { const char *e = getenv("UYA_RC_TRACE_MEM"); snprintf(mem, sizeof mem, "%s", e ? e : ""); }
|
||||
char t[512]; int n = 0;
|
||||
for (const char *p = mem; *p && n < 400;) {
|
||||
unsigned a = 0, l = 0;
|
||||
if (sscanf(p, "%x,%x", &a, &l) != 2) break;
|
||||
n += snprintf(t + n, sizeof t - n, " [%X]", a);
|
||||
for (unsigned i = 0; i < l && n < 480; i++) n += snprintf(t + n, sizeof t - n, "%02X", *(const u8 *)(uintptr_t)(a + i));
|
||||
while (*p && *p != ';') p++;
|
||||
if (*p == ';') p++;
|
||||
}
|
||||
t[n] = 0;
|
||||
uya_log("v%u %s(%08X, %08X, %08X, %08X) ra %08X%s", uya_frame_count(), name, (u32)c->r[4].d[0], (u32)c->r[5].d[0],
|
||||
(u32)c->r[6].d[0], (u32)c->r[7].d[0], (u32)c->r[31].d[0], t);
|
||||
}
|
||||
|
||||
/* testing: a heuristic backtrace of translated code: the words on its stack
|
||||
* (from rc_sp, which the thunks into host code set) that look like code
|
||||
* addresses (return addresses the translated functions saved) */
|
||||
const char *rc_stack_scan(void)
|
||||
{
|
||||
static char t[512];
|
||||
int n = 0, k = 0;
|
||||
const u32 *sp = (const u32 *)(uintptr_t)rc_sp;
|
||||
t[0] = 0;
|
||||
for (int i = 0; sp && i < 1024 && k < 12; i += 2) {
|
||||
u32 v = sp[i];
|
||||
if (v >= 0x00112380u && v < 0x00300000u && !(v & 3) && (v & 0xFFFF) && v != 0x00160000u && v != 0x00170000u) {
|
||||
n += snprintf(t + n, sizeof t - n, " %08X@%d", v, i * 4); k++;
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,307 @@
|
||||
/* Tables for the 989snd host driver (host/src/rac1/snd989.c): the SPU2's note-to-pitch table,
|
||||
* 989snd's pan table and the SPU's 4-tap Gaussian interpolation table. Values from the
|
||||
* jak-project 989snd reimplementation (ISC licence, see THIRD_PARTY_NOTICES.md). */
|
||||
static const unsigned short snd_note_pitch[140] = {
|
||||
0x8000, 0x879C, 0x8FAC, 0x9837, 0xA145, 0xAADC, 0xB504, 0xBFC8, 0xCB2F, 0xD744, 0xE411, 0xF1A1,
|
||||
0x8000, 0x800E, 0x801D, 0x802C, 0x803B, 0x804A, 0x8058, 0x8067, 0x8076, 0x8085, 0x8094, 0x80A3,
|
||||
0x80B1, 0x80C0, 0x80CF, 0x80DE, 0x80ED, 0x80FC, 0x810B, 0x811A, 0x8129, 0x8138, 0x8146, 0x8155,
|
||||
0x8164, 0x8173, 0x8182, 0x8191, 0x81A0, 0x81AF, 0x81BE, 0x81CD, 0x81DC, 0x81EB, 0x81FA, 0x8209,
|
||||
0x8218, 0x8227, 0x8236, 0x8245, 0x8254, 0x8263, 0x8272, 0x8282, 0x8291, 0x82A0, 0x82AF, 0x82BE,
|
||||
0x82CD, 0x82DC, 0x82EB, 0x82FA, 0x830A, 0x8319, 0x8328, 0x8337, 0x8346, 0x8355, 0x8364, 0x8374,
|
||||
0x8383, 0x8392, 0x83A1, 0x83B0, 0x83C0, 0x83CF, 0x83DE, 0x83ED, 0x83FD, 0x840C, 0x841B, 0x842A,
|
||||
0x843A, 0x8449, 0x8458, 0x8468, 0x8477, 0x8486, 0x8495, 0x84A5, 0x84B4, 0x84C3, 0x84D3, 0x84E2,
|
||||
0x84F1, 0x8501, 0x8510, 0x8520, 0x852F, 0x853E, 0x854E, 0x855D, 0x856D, 0x857C, 0x858B, 0x859B,
|
||||
0x85AA, 0x85BA, 0x85C9, 0x85D9, 0x85E8, 0x85F8, 0x8607, 0x8617, 0x8626, 0x8636, 0x8645, 0x8655,
|
||||
0x8664, 0x8674, 0x8683, 0x8693, 0x86A2, 0x86B2, 0x86C1, 0x86D1, 0x86E0, 0x86F0, 0x8700, 0x870F,
|
||||
0x871F, 0x872E, 0x873E, 0x874E, 0x875D, 0x876D, 0x877D, 0x878C,
|
||||
};
|
||||
static const short snd_pan_table[181][2] = {
|
||||
{0x3fff, 0x0000}, {0x3ffe, 0x008e}, {0x3ffc, 0x011d}, {0x3ff9, 0x01ac}, {0x3ff5, 0x023b}, {0x3fef, 0x02ca},
|
||||
{0x3fe8, 0x0359}, {0x3fe0, 0x03e8}, {0x3fd7, 0x0476}, {0x3fcc, 0x0505}, {0x3fc0, 0x0593}, {0x3fb3, 0x0622},
|
||||
{0x3fa5, 0x06b0}, {0x3f95, 0x073e}, {0x3f84, 0x07cc}, {0x3f72, 0x085a}, {0x3f5f, 0x08e8}, {0x3f4b, 0x0975},
|
||||
{0x3f35, 0x0a02}, {0x3f1e, 0x0a8f}, {0x3f06, 0x0b1c}, {0x3eec, 0x0ba9}, {0x3ed1, 0x0c36}, {0x3eb6, 0x0cc2},
|
||||
{0x3e98, 0x0d4e}, {0x3e7a, 0x0dd9}, {0x3e5b, 0x0e65}, {0x3e3a, 0x0ef0}, {0x3e18, 0x0f7b}, {0x3df5, 0x1005},
|
||||
{0x3dd0, 0x1090}, {0x3dab, 0x111a}, {0x3d84, 0x11a3}, {0x3d5c, 0x122d}, {0x3d33, 0x12b5}, {0x3d08, 0x133e},
|
||||
{0x3cdd, 0x13c6}, {0x3cb0, 0x144e}, {0x3c82, 0x14d5}, {0x3c53, 0x155c}, {0x3c22, 0x15e3}, {0x3bf1, 0x1669},
|
||||
{0x3bbe, 0x16ef}, {0x3b8b, 0x1774}, {0x3b56, 0x17f9}, {0x3b1f, 0x187d}, {0x3ae8, 0x1901}, {0x3ab0, 0x1984},
|
||||
{0x3a76, 0x1a07}, {0x3a3b, 0x1a89}, {0x3a00, 0x1b0b}, {0x39c3, 0x1b8d}, {0x3984, 0x1c0d}, {0x3945, 0x1c8e},
|
||||
{0x3905, 0x1d0d}, {0x38c3, 0x1d8c}, {0x3881, 0x1e0b}, {0x383d, 0x1e89}, {0x37f8, 0x1f06}, {0x37b3, 0x1f83},
|
||||
{0x376c, 0x1fff}, {0x3724, 0x207b}, {0x36da, 0x20f5}, {0x3690, 0x2170}, {0x3645, 0x21e9}, {0x35f9, 0x2262},
|
||||
{0x35ab, 0x22da}, {0x355d, 0x2352}, {0x350e, 0x23c9}, {0x34bd, 0x243f}, {0x346c, 0x24b4}, {0x3419, 0x2529},
|
||||
{0x33c6, 0x259d}, {0x3371, 0x2610}, {0x331c, 0x2683}, {0x32c5, 0x26f5}, {0x326d, 0x2766}, {0x3215, 0x27d6},
|
||||
{0x31bb, 0x2846}, {0x3161, 0x28b4}, {0x3106, 0x2922}, {0x30a9, 0x298f}, {0x304c, 0x29fc}, {0x2fee, 0x2a67},
|
||||
{0x2f8e, 0x2ad2}, {0x2f2e, 0x2b3c}, {0x2ecd, 0x2ba5}, {0x2e6b, 0x2c0d}, {0x2e08, 0x2c74}, {0x2da5, 0x2cda},
|
||||
{0x2d40, 0x2d40}, {0x2cda, 0x2da5}, {0x2c74, 0x2e08}, {0x2c0d, 0x2e6b}, {0x2ba5, 0x2ecd}, {0x2b3c, 0x2f2e},
|
||||
{0x2ad2, 0x2f8e}, {0x2a67, 0x2fee}, {0x29fc, 0x304c}, {0x298f, 0x30a9}, {0x2922, 0x3106}, {0x28b4, 0x3161},
|
||||
{0x2846, 0x31bb}, {0x27d6, 0x3215}, {0x2766, 0x326d}, {0x26f5, 0x32c5}, {0x2683, 0x331c}, {0x2610, 0x3371},
|
||||
{0x259d, 0x33c6}, {0x2529, 0x3419}, {0x24b4, 0x346c}, {0x243f, 0x34bd}, {0x23c9, 0x350e}, {0x2352, 0x355d},
|
||||
{0x22da, 0x35ab}, {0x2262, 0x35f9}, {0x21e9, 0x3645}, {0x2170, 0x3690}, {0x20f5, 0x36da}, {0x207b, 0x3724},
|
||||
{0x1fff, 0x376c}, {0x1f83, 0x37b3}, {0x1f06, 0x37f8}, {0x1e89, 0x383d}, {0x1e0b, 0x3881}, {0x1d8c, 0x38c3},
|
||||
{0x1d0d, 0x3905}, {0x1c8e, 0x3945}, {0x1c0d, 0x3984}, {0x1b8d, 0x39c3}, {0x1b0b, 0x3a00}, {0x1a89, 0x3a3b},
|
||||
{0x1a07, 0x3a76}, {0x1984, 0x3ab0}, {0x1901, 0x3ae8}, {0x187d, 0x3b1f}, {0x17f9, 0x3b56}, {0x1774, 0x3b8b},
|
||||
{0x16ef, 0x3bbe}, {0x1669, 0x3bf1}, {0x15e3, 0x3c22}, {0x155c, 0x3c53}, {0x14d5, 0x3c82}, {0x144e, 0x3cb0},
|
||||
{0x13c6, 0x3cdd}, {0x133e, 0x3d08}, {0x12b5, 0x3d33}, {0x122d, 0x3d5c}, {0x11a3, 0x3d84}, {0x111a, 0x3dab},
|
||||
{0x1090, 0x3dd0}, {0x1005, 0x3df5}, {0x0f7b, 0x3e18}, {0x0ef0, 0x3e3a}, {0x0e65, 0x3e5b}, {0x0dd9, 0x3e7a},
|
||||
{0x0d4e, 0x3e98}, {0x0cc2, 0x3eb6}, {0x0c36, 0x3ed1}, {0x0ba9, 0x3eec}, {0x0b1c, 0x3f06}, {0x0a8f, 0x3f1e},
|
||||
{0x0a02, 0x3f35}, {0x0975, 0x3f4b}, {0x08e8, 0x3f5f}, {0x085a, 0x3f72}, {0x07cc, 0x3f84}, {0x073e, 0x3f95},
|
||||
{0x06b0, 0x3fa5}, {0x0622, 0x3fb3}, {0x0593, 0x3fc0}, {0x0505, 0x3fcc}, {0x0476, 0x3fd7}, {0x03e8, 0x3fe0},
|
||||
{0x0359, 0x3fe8}, {0x02ca, 0x3fef}, {0x023b, 0x3ff5}, {0x01ac, 0x3ff9}, {0x011d, 0x3ffc}, {0x008e, 0x3ffe},
|
||||
};
|
||||
static const short snd_interp[256][4] = {
|
||||
{0x12C7, 0x59B3, 0x1307, -0x0001},
|
||||
{0x1288, 0x59B2, 0x1347, -0x0001},
|
||||
{0x1249, 0x59B0, 0x1388, -0x0001},
|
||||
{0x120B, 0x59AD, 0x13C9, -0x0001},
|
||||
{0x11CD, 0x59A9, 0x140B, -0x0001},
|
||||
{0x118F, 0x59A4, 0x144D, -0x0001},
|
||||
{0x1153, 0x599E, 0x1490, -0x0001},
|
||||
{0x1116, 0x5997, 0x14D4, -0x0001},
|
||||
{0x10DB, 0x598F, 0x1517, -0x0001},
|
||||
{0x109F, 0x5986, 0x155C, -0x0001},
|
||||
{0x1065, 0x597C, 0x15A0, -0x0001},
|
||||
{0x102A, 0x5971, 0x15E6, -0x0001},
|
||||
{0x0FF1, 0x5965, 0x162C, -0x0001},
|
||||
{0x0FB7, 0x5958, 0x1672, -0x0001},
|
||||
{0x0F7F, 0x5949, 0x16B9, -0x0001},
|
||||
{0x0F46, 0x593A, 0x1700, -0x0001},
|
||||
{0x0F0F, 0x592A, 0x1747, 0x0000},
|
||||
{0x0ED7, 0x5919, 0x1790, 0x0000},
|
||||
{0x0EA1, 0x5907, 0x17D8, 0x0000},
|
||||
{0x0E6B, 0x58F4, 0x1821, 0x0000},
|
||||
{0x0E35, 0x58E0, 0x186B, 0x0000},
|
||||
{0x0E00, 0x58CB, 0x18B5, 0x0000},
|
||||
{0x0DCB, 0x58B5, 0x1900, 0x0000},
|
||||
{0x0D97, 0x589E, 0x194B, 0x0001},
|
||||
{0x0D63, 0x5886, 0x1996, 0x0001},
|
||||
{0x0D30, 0x586D, 0x19E2, 0x0001},
|
||||
{0x0CFD, 0x5853, 0x1A2E, 0x0001},
|
||||
{0x0CCB, 0x5838, 0x1A7B, 0x0002},
|
||||
{0x0C99, 0x581C, 0x1AC8, 0x0002},
|
||||
{0x0C68, 0x57FF, 0x1B16, 0x0002},
|
||||
{0x0C38, 0x57E2, 0x1B64, 0x0003},
|
||||
{0x0C07, 0x57C3, 0x1BB3, 0x0003},
|
||||
{0x0BD8, 0x57A3, 0x1C02, 0x0003},
|
||||
{0x0BA9, 0x5782, 0x1C51, 0x0004},
|
||||
{0x0B7A, 0x5761, 0x1CA1, 0x0004},
|
||||
{0x0B4C, 0x573E, 0x1CF1, 0x0005},
|
||||
{0x0B1E, 0x571B, 0x1D42, 0x0005},
|
||||
{0x0AF1, 0x56F6, 0x1D93, 0x0006},
|
||||
{0x0AC4, 0x56D1, 0x1DE5, 0x0007},
|
||||
{0x0A98, 0x56AB, 0x1E37, 0x0007},
|
||||
{0x0A6C, 0x5684, 0x1E89, 0x0008},
|
||||
{0x0A40, 0x565B, 0x1EDC, 0x0009},
|
||||
{0x0A16, 0x5632, 0x1F2F, 0x0009},
|
||||
{0x09EB, 0x5609, 0x1F82, 0x000A},
|
||||
{0x09C1, 0x55DE, 0x1FD6, 0x000B},
|
||||
{0x0998, 0x55B2, 0x202A, 0x000C},
|
||||
{0x096F, 0x5585, 0x207F, 0x000D},
|
||||
{0x0946, 0x5558, 0x20D4, 0x000E},
|
||||
{0x091E, 0x5529, 0x2129, 0x000F},
|
||||
{0x08F7, 0x54FA, 0x217F, 0x0010},
|
||||
{0x08D0, 0x54CA, 0x21D5, 0x0011},
|
||||
{0x08A9, 0x5499, 0x222C, 0x0012},
|
||||
{0x0883, 0x5467, 0x2282, 0x0013},
|
||||
{0x085D, 0x5434, 0x22DA, 0x0015},
|
||||
{0x0838, 0x5401, 0x2331, 0x0016},
|
||||
{0x0813, 0x53CC, 0x2389, 0x0018},
|
||||
{0x07EF, 0x5397, 0x23E1, 0x0019},
|
||||
{0x07CB, 0x5361, 0x2439, 0x001B},
|
||||
{0x07A7, 0x532A, 0x2492, 0x001C},
|
||||
{0x0784, 0x52F3, 0x24EB, 0x001E},
|
||||
{0x0762, 0x52BA, 0x2545, 0x0020},
|
||||
{0x0740, 0x5281, 0x259E, 0x0021},
|
||||
{0x071E, 0x5247, 0x25F8, 0x0023},
|
||||
{0x06FD, 0x520C, 0x2653, 0x0025},
|
||||
{0x06DC, 0x51D0, 0x26AD, 0x0027},
|
||||
{0x06BB, 0x5194, 0x2708, 0x0029},
|
||||
{0x069B, 0x5156, 0x2763, 0x002C},
|
||||
{0x067C, 0x5118, 0x27BE, 0x002E},
|
||||
{0x065C, 0x50DA, 0x281A, 0x0030},
|
||||
{0x063E, 0x509A, 0x2876, 0x0033},
|
||||
{0x061F, 0x505A, 0x28D2, 0x0035},
|
||||
{0x0601, 0x5019, 0x292E, 0x0038},
|
||||
{0x05E4, 0x4FD7, 0x298B, 0x003A},
|
||||
{0x05C7, 0x4F95, 0x29E7, 0x003D},
|
||||
{0x05AA, 0x4F52, 0x2A44, 0x0040},
|
||||
{0x058E, 0x4F0E, 0x2AA1, 0x0043},
|
||||
{0x0572, 0x4EC9, 0x2AFF, 0x0046},
|
||||
{0x0556, 0x4E84, 0x2B5C, 0x0049},
|
||||
{0x053B, 0x4E3E, 0x2BBA, 0x004D},
|
||||
{0x0520, 0x4DF7, 0x2C18, 0x0050},
|
||||
{0x0506, 0x4DB0, 0x2C76, 0x0054},
|
||||
{0x04EC, 0x4D68, 0x2CD4, 0x0057},
|
||||
{0x04D2, 0x4D20, 0x2D33, 0x005B},
|
||||
{0x04B9, 0x4CD7, 0x2D91, 0x005F},
|
||||
{0x04A0, 0x4C8D, 0x2DF0, 0x0063},
|
||||
{0x0488, 0x4C42, 0x2E4F, 0x0067},
|
||||
{0x0470, 0x4BF7, 0x2EAE, 0x006B},
|
||||
{0x0458, 0x4BAC, 0x2F0D, 0x006F},
|
||||
{0x0441, 0x4B5F, 0x2F6C, 0x0074},
|
||||
{0x042A, 0x4B13, 0x2FCC, 0x0078},
|
||||
{0x0413, 0x4AC5, 0x302B, 0x007D},
|
||||
{0x03FC, 0x4A77, 0x308B, 0x0082},
|
||||
{0x03E7, 0x4A29, 0x30EA, 0x0087},
|
||||
{0x03D1, 0x49D9, 0x314A, 0x008C},
|
||||
{0x03BC, 0x498A, 0x31AA, 0x0091},
|
||||
{0x03A7, 0x493A, 0x3209, 0x0096},
|
||||
{0x0392, 0x48E9, 0x3269, 0x009C},
|
||||
{0x037E, 0x4898, 0x32C9, 0x00A1},
|
||||
{0x036A, 0x4846, 0x3329, 0x00A7},
|
||||
{0x0356, 0x47F4, 0x3389, 0x00AD},
|
||||
{0x0343, 0x47A1, 0x33E9, 0x00B3},
|
||||
{0x0330, 0x474E, 0x3449, 0x00BA},
|
||||
{0x031D, 0x46FA, 0x34A9, 0x00C0},
|
||||
{0x030B, 0x46A6, 0x3509, 0x00C7},
|
||||
{0x02F9, 0x4651, 0x3569, 0x00CD},
|
||||
{0x02E7, 0x45FC, 0x35C9, 0x00D4},
|
||||
{0x02D6, 0x45A6, 0x3629, 0x00DB},
|
||||
{0x02C4, 0x4550, 0x3689, 0x00E3},
|
||||
{0x02B4, 0x44FA, 0x36E8, 0x00EA},
|
||||
{0x02A3, 0x44A3, 0x3748, 0x00F2},
|
||||
{0x0293, 0x444C, 0x37A8, 0x00FA},
|
||||
{0x0283, 0x43F4, 0x3807, 0x0101},
|
||||
{0x0273, 0x439C, 0x3867, 0x010A},
|
||||
{0x0264, 0x4344, 0x38C6, 0x0112},
|
||||
{0x0255, 0x42EB, 0x3926, 0x011B},
|
||||
{0x0246, 0x4292, 0x3985, 0x0123},
|
||||
{0x0237, 0x4239, 0x39E4, 0x012C},
|
||||
{0x0229, 0x41DF, 0x3A43, 0x0135},
|
||||
{0x021B, 0x4185, 0x3AA2, 0x013F},
|
||||
{0x020D, 0x412A, 0x3B00, 0x0148},
|
||||
{0x0200, 0x40D0, 0x3B5F, 0x0152},
|
||||
{0x01F2, 0x4074, 0x3BBD, 0x015C},
|
||||
{0x01E5, 0x4019, 0x3C1B, 0x0166},
|
||||
{0x01D9, 0x3FBD, 0x3C79, 0x0171},
|
||||
{0x01CC, 0x3F62, 0x3CD7, 0x017B},
|
||||
{0x01C0, 0x3F05, 0x3D35, 0x0186},
|
||||
{0x01B4, 0x3EA9, 0x3D92, 0x0191},
|
||||
{0x01A8, 0x3E4C, 0x3DEF, 0x019C},
|
||||
{0x019C, 0x3DEF, 0x3E4C, 0x01A8},
|
||||
{0x0191, 0x3D92, 0x3EA9, 0x01B4},
|
||||
{0x0186, 0x3D35, 0x3F05, 0x01C0},
|
||||
{0x017B, 0x3CD7, 0x3F62, 0x01CC},
|
||||
{0x0171, 0x3C79, 0x3FBD, 0x01D9},
|
||||
{0x0166, 0x3C1B, 0x4019, 0x01E5},
|
||||
{0x015C, 0x3BBD, 0x4074, 0x01F2},
|
||||
{0x0152, 0x3B5F, 0x40D0, 0x0200},
|
||||
{0x0148, 0x3B00, 0x412A, 0x020D},
|
||||
{0x013F, 0x3AA2, 0x4185, 0x021B},
|
||||
{0x0135, 0x3A43, 0x41DF, 0x0229},
|
||||
{0x012C, 0x39E4, 0x4239, 0x0237},
|
||||
{0x0123, 0x3985, 0x4292, 0x0246},
|
||||
{0x011B, 0x3926, 0x42EB, 0x0255},
|
||||
{0x0112, 0x38C6, 0x4344, 0x0264},
|
||||
{0x010A, 0x3867, 0x439C, 0x0273},
|
||||
{0x0101, 0x3807, 0x43F4, 0x0283},
|
||||
{0x00FA, 0x37A8, 0x444C, 0x0293},
|
||||
{0x00F2, 0x3748, 0x44A3, 0x02A3},
|
||||
{0x00EA, 0x36E8, 0x44FA, 0x02B4},
|
||||
{0x00E3, 0x3689, 0x4550, 0x02C4},
|
||||
{0x00DB, 0x3629, 0x45A6, 0x02D6},
|
||||
{0x00D4, 0x35C9, 0x45FC, 0x02E7},
|
||||
{0x00CD, 0x3569, 0x4651, 0x02F9},
|
||||
{0x00C7, 0x3509, 0x46A6, 0x030B},
|
||||
{0x00C0, 0x34A9, 0x46FA, 0x031D},
|
||||
{0x00BA, 0x3449, 0x474E, 0x0330},
|
||||
{0x00B3, 0x33E9, 0x47A1, 0x0343},
|
||||
{0x00AD, 0x3389, 0x47F4, 0x0356},
|
||||
{0x00A7, 0x3329, 0x4846, 0x036A},
|
||||
{0x00A1, 0x32C9, 0x4898, 0x037E},
|
||||
{0x009C, 0x3269, 0x48E9, 0x0392},
|
||||
{0x0096, 0x3209, 0x493A, 0x03A7},
|
||||
{0x0091, 0x31AA, 0x498A, 0x03BC},
|
||||
{0x008C, 0x314A, 0x49D9, 0x03D1},
|
||||
{0x0087, 0x30EA, 0x4A29, 0x03E7},
|
||||
{0x0082, 0x308B, 0x4A77, 0x03FC},
|
||||
{0x007D, 0x302B, 0x4AC5, 0x0413},
|
||||
{0x0078, 0x2FCC, 0x4B13, 0x042A},
|
||||
{0x0074, 0x2F6C, 0x4B5F, 0x0441},
|
||||
{0x006F, 0x2F0D, 0x4BAC, 0x0458},
|
||||
{0x006B, 0x2EAE, 0x4BF7, 0x0470},
|
||||
{0x0067, 0x2E4F, 0x4C42, 0x0488},
|
||||
{0x0063, 0x2DF0, 0x4C8D, 0x04A0},
|
||||
{0x005F, 0x2D91, 0x4CD7, 0x04B9},
|
||||
{0x005B, 0x2D33, 0x4D20, 0x04D2},
|
||||
{0x0057, 0x2CD4, 0x4D68, 0x04EC},
|
||||
{0x0054, 0x2C76, 0x4DB0, 0x0506},
|
||||
{0x0050, 0x2C18, 0x4DF7, 0x0520},
|
||||
{0x004D, 0x2BBA, 0x4E3E, 0x053B},
|
||||
{0x0049, 0x2B5C, 0x4E84, 0x0556},
|
||||
{0x0046, 0x2AFF, 0x4EC9, 0x0572},
|
||||
{0x0043, 0x2AA1, 0x4F0E, 0x058E},
|
||||
{0x0040, 0x2A44, 0x4F52, 0x05AA},
|
||||
{0x003D, 0x29E7, 0x4F95, 0x05C7},
|
||||
{0x003A, 0x298B, 0x4FD7, 0x05E4},
|
||||
{0x0038, 0x292E, 0x5019, 0x0601},
|
||||
{0x0035, 0x28D2, 0x505A, 0x061F},
|
||||
{0x0033, 0x2876, 0x509A, 0x063E},
|
||||
{0x0030, 0x281A, 0x50DA, 0x065C},
|
||||
{0x002E, 0x27BE, 0x5118, 0x067C},
|
||||
{0x002C, 0x2763, 0x5156, 0x069B},
|
||||
{0x0029, 0x2708, 0x5194, 0x06BB},
|
||||
{0x0027, 0x26AD, 0x51D0, 0x06DC},
|
||||
{0x0025, 0x2653, 0x520C, 0x06FD},
|
||||
{0x0023, 0x25F8, 0x5247, 0x071E},
|
||||
{0x0021, 0x259E, 0x5281, 0x0740},
|
||||
{0x0020, 0x2545, 0x52BA, 0x0762},
|
||||
{0x001E, 0x24EB, 0x52F3, 0x0784},
|
||||
{0x001C, 0x2492, 0x532A, 0x07A7},
|
||||
{0x001B, 0x2439, 0x5361, 0x07CB},
|
||||
{0x0019, 0x23E1, 0x5397, 0x07EF},
|
||||
{0x0018, 0x2389, 0x53CC, 0x0813},
|
||||
{0x0016, 0x2331, 0x5401, 0x0838},
|
||||
{0x0015, 0x22DA, 0x5434, 0x085D},
|
||||
{0x0013, 0x2282, 0x5467, 0x0883},
|
||||
{0x0012, 0x222C, 0x5499, 0x08A9},
|
||||
{0x0011, 0x21D5, 0x54CA, 0x08D0},
|
||||
{0x0010, 0x217F, 0x54FA, 0x08F7},
|
||||
{0x000F, 0x2129, 0x5529, 0x091E},
|
||||
{0x000E, 0x20D4, 0x5558, 0x0946},
|
||||
{0x000D, 0x207F, 0x5585, 0x096F},
|
||||
{0x000C, 0x202A, 0x55B2, 0x0998},
|
||||
{0x000B, 0x1FD6, 0x55DE, 0x09C1},
|
||||
{0x000A, 0x1F82, 0x5609, 0x09EB},
|
||||
{0x0009, 0x1F2F, 0x5632, 0x0A16},
|
||||
{0x0009, 0x1EDC, 0x565B, 0x0A40},
|
||||
{0x0008, 0x1E89, 0x5684, 0x0A6C},
|
||||
{0x0007, 0x1E37, 0x56AB, 0x0A98},
|
||||
{0x0007, 0x1DE5, 0x56D1, 0x0AC4},
|
||||
{0x0006, 0x1D93, 0x56F6, 0x0AF1},
|
||||
{0x0005, 0x1D42, 0x571B, 0x0B1E},
|
||||
{0x0005, 0x1CF1, 0x573E, 0x0B4C},
|
||||
{0x0004, 0x1CA1, 0x5761, 0x0B7A},
|
||||
{0x0004, 0x1C51, 0x5782, 0x0BA9},
|
||||
{0x0003, 0x1C02, 0x57A3, 0x0BD8},
|
||||
{0x0003, 0x1BB3, 0x57C3, 0x0C07},
|
||||
{0x0003, 0x1B64, 0x57E2, 0x0C38},
|
||||
{0x0002, 0x1B16, 0x57FF, 0x0C68},
|
||||
{0x0002, 0x1AC8, 0x581C, 0x0C99},
|
||||
{0x0002, 0x1A7B, 0x5838, 0x0CCB},
|
||||
{0x0001, 0x1A2E, 0x5853, 0x0CFD},
|
||||
{0x0001, 0x19E2, 0x586D, 0x0D30},
|
||||
{0x0001, 0x1996, 0x5886, 0x0D63},
|
||||
{0x0001, 0x194B, 0x589E, 0x0D97},
|
||||
{0x0000, 0x1900, 0x58B5, 0x0DCB},
|
||||
{0x0000, 0x18B5, 0x58CB, 0x0E00},
|
||||
{0x0000, 0x186B, 0x58E0, 0x0E35},
|
||||
{0x0000, 0x1821, 0x58F4, 0x0E6B},
|
||||
{0x0000, 0x17D8, 0x5907, 0x0EA1},
|
||||
{0x0000, 0x1790, 0x5919, 0x0ED7},
|
||||
{0x0000, 0x1747, 0x592A, 0x0F0F},
|
||||
{-0x0001, 0x1700, 0x593A, 0x0F46},
|
||||
{-0x0001, 0x16B9, 0x5949, 0x0F7F},
|
||||
{-0x0001, 0x1672, 0x5958, 0x0FB7},
|
||||
{-0x0001, 0x162C, 0x5965, 0x0FF1},
|
||||
{-0x0001, 0x15E6, 0x5971, 0x102A},
|
||||
{-0x0001, 0x15A0, 0x597C, 0x1065},
|
||||
{-0x0001, 0x155C, 0x5986, 0x109F},
|
||||
{-0x0001, 0x1517, 0x598F, 0x10DB},
|
||||
{-0x0001, 0x14D4, 0x5997, 0x1116},
|
||||
{-0x0001, 0x1490, 0x599E, 0x1153},
|
||||
{-0x0001, 0x144D, 0x59A4, 0x118F},
|
||||
{-0x0001, 0x140B, 0x59A9, 0x11CD},
|
||||
{-0x0001, 0x13C9, 0x59AD, 0x120B},
|
||||
{-0x0001, 0x1388, 0x59B0, 0x1249},
|
||||
{-0x0001, 0x1347, 0x59B2, 0x1288},
|
||||
{-0x0001, 0x1307, 0x59B3, 0x12C7},
|
||||
};
|
||||
@@ -0,0 +1,164 @@
|
||||
/* Host runtime: loads data sections from the user's own ELFs into PS2 RAM,
|
||||
* writes x86 jumps at the PS2 address of every host-implemented function,
|
||||
* and keeps the log of unimplemented calls. */
|
||||
#include "uya_runtime.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
/* ---- ELF32 little-endian section loader ---- */
|
||||
typedef struct { uint8_t ident[16]; uint16_t type, machine; uint32_t version, entry, phoff, shoff, flags;
|
||||
uint16_t ehsize, phentsize, phnum, shentsize, shnum, shstrndx; } Ehdr;
|
||||
typedef struct { uint32_t name, type, flags, addr, offset, size, link, info, addralign, entsize; } Shdr;
|
||||
|
||||
/* A file in the disc image's root directory (ISO 9660), read whole: the
|
||||
* static recompilation runs off the disc, with nothing else from the game. */
|
||||
int uya_disc_read(uint32_t lsn, uint32_t count, void *buf);
|
||||
uint8_t *uya_disc_file(const char *name, uint32_t *size)
|
||||
{
|
||||
uint8_t pvd[2048];
|
||||
if (uya_disc_read(16, 1, pvd) || memcmp(pvd + 1, "CD001", 5)) return 0;
|
||||
uint32_t dlsn = *(uint32_t *)(pvd + 156 + 2), dlen = *(uint32_t *)(pvd + 156 + 10);
|
||||
uint32_t nsec = (dlen + 2047) / 2048;
|
||||
uint8_t *dir = malloc(nsec * 2048);
|
||||
if (!dir || uya_disc_read(dlsn, nsec, dir)) { free(dir); return 0; }
|
||||
size_t nl = strlen(name);
|
||||
for (uint32_t o = 0; o < nsec * 2048;) {
|
||||
uint8_t len = dir[o];
|
||||
if (!len) { o = (o / 2048 + 1) * 2048; continue; }
|
||||
uint8_t fl = dir[o + 32];
|
||||
const char *fn = (const char *)dir + o + 33;
|
||||
if (fl >= nl && !memcmp(fn, name, nl) && (fl == nl || fn[nl] == ';')) {
|
||||
uint32_t flsn = *(uint32_t *)(dir + o + 2), fsz = *(uint32_t *)(dir + o + 10);
|
||||
free(dir);
|
||||
uint8_t *d = malloc(((fsz + 2047) / 2048) * 2048);
|
||||
if (!d || uya_disc_read(flsn, (fsz + 2047) / 2048, d)) { free(d); return 0; }
|
||||
*size = fsz;
|
||||
return d;
|
||||
}
|
||||
o += len;
|
||||
}
|
||||
free(dir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uya_load_elf_sections(const char *path, const char *const *sections)
|
||||
{
|
||||
uint8_t *d;
|
||||
long n;
|
||||
if (!strncmp(path, "disc:", 5)) { /* from the disc image */
|
||||
uint32_t sz;
|
||||
d = uya_disc_file(path + 5, &sz);
|
||||
if (!d) { uya_log("%s is not on the disc image", path + 5); return -1; }
|
||||
n = (long)sz;
|
||||
} else {
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) { uya_log("cannot open %s", path); return -1; }
|
||||
fseek(f, 0, SEEK_END); n = ftell(f); fseek(f, 0, SEEK_SET);
|
||||
d = malloc(n);
|
||||
if (!d || fread(d, 1, n, f) != (size_t)n) { fclose(f); return -1; }
|
||||
fclose(f);
|
||||
}
|
||||
Ehdr *e = (Ehdr *)d;
|
||||
if (memcmp(e->ident, "\x7f" "ELF", 4) || e->ident[4] != 1 || e->machine != 8) {
|
||||
uya_log("%s is not a 32-bit MIPS ELF", path); free(d); return -1;
|
||||
}
|
||||
Shdr *sh = (Shdr *)(d + e->shoff);
|
||||
const char *strtab = (const char *)d + sh[e->shstrndx].offset;
|
||||
int loaded = 0;
|
||||
for (int i = 0; i < e->shnum; i++) {
|
||||
const char *nm = strtab + sh[i].name;
|
||||
for (const char *const *s = sections; *s; s++) {
|
||||
if (strcmp(nm, *s)) continue;
|
||||
uint32_t a = sh[i].addr, sz = sh[i].size;
|
||||
if (a < PS2_RAM_LO || a + sz > PS2_RAM_HI) { uya_log("%s %s out of RAM", path, nm); continue; }
|
||||
if (sh[i].type == 8 /* NOBITS */) memset((void *)(uintptr_t)a, 0, sz);
|
||||
else memcpy((void *)(uintptr_t)a, d + sh[i].offset, sz);
|
||||
uya_log("loaded %-12s 0x%08X-0x%08X from %s", nm, a, a + sz, path);
|
||||
loaded++;
|
||||
}
|
||||
}
|
||||
free(d);
|
||||
return loaded;
|
||||
}
|
||||
|
||||
/* ---- trampolines ---- */
|
||||
void uya_fill_code_traps(uint32_t lo, uint32_t hi)
|
||||
{
|
||||
memset((void *)(uintptr_t)lo, 0xCC, hi - lo); /* int3: unknown PS2 code */
|
||||
}
|
||||
|
||||
void uya_install_tramp_table(const uya_tramp_t *table, int count)
|
||||
{
|
||||
for (int i = 0; i < count; i++) {
|
||||
uint8_t *p = (uint8_t *)(uintptr_t)table[i].ps2_addr;
|
||||
int32_t rel = (int32_t)((uintptr_t)table[i].host_fn - ((uintptr_t)p + 5));
|
||||
p[0] = 0xE9; /* jmp rel32 */
|
||||
memcpy(p + 1, &rel, 4);
|
||||
}
|
||||
plat_flush_icache((void *)(uintptr_t)PS2_RAM_LO, PS2_RAM_HI - PS2_RAM_LO);
|
||||
uya_log("installed %d trampolines", count);
|
||||
}
|
||||
|
||||
void uya_install_trampolines(void) { uya_install_tramp_table(uya_tramp_table, uya_tramp_count); }
|
||||
|
||||
/* ---- logging ---- */
|
||||
static FILE *logfile;
|
||||
void uya_log_open(void)
|
||||
{
|
||||
char p[1024];
|
||||
snprintf(p, sizeof p, "%s/rac1_log.txt", plat_exe_dir());
|
||||
logfile = fopen(p, "w");
|
||||
if (logfile) fprintf(stderr, "[uya] log file: %s\n", p);
|
||||
}
|
||||
|
||||
void uya_log(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt); fprintf(stderr, "[uya] "); vfprintf(stderr, fmt, ap); fputc('\n', stderr); va_end(ap);
|
||||
fflush(stderr);
|
||||
if (logfile) {
|
||||
va_start(ap, fmt); fprintf(logfile, "[uya] "); vfprintf(logfile, fmt, ap); fputc('\n', logfile); va_end(ap);
|
||||
fflush(logfile);
|
||||
}
|
||||
}
|
||||
|
||||
#define MAXU 4096
|
||||
static struct { const char *name; uint32_t addr; unsigned count; } unimpl[MAXU];
|
||||
static int nunimpl;
|
||||
static unsigned long long total_calls;
|
||||
|
||||
static int bump(const char *name, uint32_t addr)
|
||||
{
|
||||
total_calls++;
|
||||
for (int i = 0; i < nunimpl; i++)
|
||||
if ((name && unimpl[i].name == name) || (!name && unimpl[i].addr == addr)) return ++unimpl[i].count;
|
||||
if (nunimpl < MAXU) { unimpl[nunimpl].name = name; unimpl[nunimpl].addr = addr; unimpl[nunimpl].count = 1; nunimpl++; }
|
||||
return 1;
|
||||
}
|
||||
|
||||
void uya_unimpl(const char *name)
|
||||
{
|
||||
if (bump(name, 0) == 1) uya_log("unimplemented game function %s", name);
|
||||
}
|
||||
|
||||
static unsigned long long trap_limit = 2000000;
|
||||
void uya_trap_call(uint32_t ps2_addr, uint32_t ret_addr, const uint32_t *args)
|
||||
{
|
||||
if (bump(0, ps2_addr) == 1)
|
||||
uya_log("unimplemented call to PS2 0x%08X (from host 0x%08X) args %08X %08X %08X %08X",
|
||||
ps2_addr, ret_addr, args[0], args[1], args[2], args[3]);
|
||||
(void)trap_limit;
|
||||
}
|
||||
|
||||
void uya_report(void)
|
||||
{
|
||||
static int done;
|
||||
if (done++) return;
|
||||
uya_log("---- %d distinct unimplemented functions, %llu calls ----", nunimpl, total_calls);
|
||||
for (int i = 0; i < nunimpl; i++) {
|
||||
if (unimpl[i].name) uya_log(" %8u %s", unimpl[i].count, unimpl[i].name);
|
||||
else uya_log(" %8u PS2 0x%08X", unimpl[i].count, unimpl[i].addr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/* Disc access: the engine core's CD read path, served from your own disc image.
|
||||
*
|
||||
* The game reads 2048-byte sectors by LSN. boot_elf reads the table of
|
||||
* contents (16 sectors at LSN 1001) into 0x160C40 before it starts the menu
|
||||
* (uyafn_13D2E0); uya_disc_boot() does the same. Reads finish immediately. */
|
||||
#include "uya_host.h"
|
||||
#include "uya_runtime.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static FILE *disc;
|
||||
static void (*cd_callback)(s32); /* sceCdCallback-style completion handler */
|
||||
static int cd_pending; /* a read finished; its callback is still owed */
|
||||
static unsigned long long nreads, nsectors;
|
||||
|
||||
#ifdef _WIN32
|
||||
int _fseeki64(FILE *, long long, int);
|
||||
#define SEEK64(f, o) _fseeki64(f, (long long)(o), SEEK_SET)
|
||||
#else
|
||||
#define SEEK64(f, o) fseeko(f, (off_t)(o), SEEK_SET)
|
||||
#endif
|
||||
|
||||
int uya_disc_open(const char *path)
|
||||
{
|
||||
disc = fopen(path, "rb");
|
||||
if (!disc) { uya_log("cannot open disc image %s", path); return -1; }
|
||||
uya_log("disc image: %s", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_sectors_q(u32 lsn, u32 count, void *buf, int quiet);
|
||||
static int read_sectors(u32 lsn, u32 count, void *buf) { return read_sectors_q(lsn, count, buf, 0); }
|
||||
static int read_sectors_q(u32 lsn, u32 count, void *buf, int quiet)
|
||||
{
|
||||
if (!disc) { uya_log("disc read LSN %u x%u: no disc image", lsn, count); return -1; }
|
||||
if (!quiet && ((u32)(uintptr_t)buf < PS2_RAM_LO || (u32)(uintptr_t)buf + count * 2048 > PS2_RAM_HI))
|
||||
uya_log("disc read LSN %u x%u into 0x%08X (outside PS2 RAM)", lsn, count, (u32)(uintptr_t)buf);
|
||||
/* Testing on a partial copy of the disc: UYA_DISC_MAP names a file with one
|
||||
* byte per sector (1 = present). Reads of absent sectors are logged and
|
||||
* appended to <map>.missing as "lsn count" so they can be fetched. */
|
||||
{
|
||||
static FILE *map; static int tried;
|
||||
if (!tried++) { const char *m = getenv("UYA_DISC_MAP"); if (m) map = fopen(m, "rb"); }
|
||||
if (map) {
|
||||
u32 miss = 0, first = 0;
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
int c = 0;
|
||||
if (fseek(map, (long)(lsn + i), SEEK_SET) == 0) c = fgetc(map);
|
||||
if (c != 1) { if (!miss) first = lsn + i; miss++; }
|
||||
}
|
||||
if (miss) {
|
||||
char p[1100]; snprintf(p, sizeof p, "%s.missing", getenv("UYA_DISC_MAP"));
|
||||
FILE *f = fopen(p, "a"); if (f) { fprintf(f, "%u %u\n", lsn, count); fclose(f); }
|
||||
uya_log("disc read LSN %u x%u: %u sectors not in the partial disc copy (first %u)", lsn, count, miss, first);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (SEEK64(disc, (unsigned long long)lsn * 2048) != 0) return -1;
|
||||
size_t got = fread(buf, 2048, count, disc);
|
||||
nreads++; nsectors += count;
|
||||
if ((nreads <= 64 && !quiet) || getenv("UYA_DISCLOG")) uya_log("disc read LSN %u x%u -> 0x%08X", lsn, count, (u32)(uintptr_t)buf);
|
||||
return got == count ? 0 : -1;
|
||||
}
|
||||
|
||||
int uya_disc_read(uint32_t lsn, uint32_t count, void *buf) { return read_sectors_q(lsn, count, buf, 1); }
|
||||
|
||||
/* 0x0013CD28: start a read of `count` sectors at `lsn` into `buf` (mode in the 4th argument). 1 = started. */
|
||||
s32 uyafn_13CD28(s32 lsn, s32 count, void *buf, void *mode)
|
||||
{
|
||||
(void)mode;
|
||||
if (read_sectors((u32)lsn, (u32)count, buf) != 0) return 0;
|
||||
cd_pending = 1; /* on the PS2 the completion interrupt comes later, after the caller marks the read busy */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Deliver a finished read's callback. Called from the functions the game polls. */
|
||||
void uya_cd_service(void)
|
||||
{
|
||||
if (cd_pending) {
|
||||
cd_pending = 0;
|
||||
if (cd_callback) cd_callback(1); /* 1 = read finished */
|
||||
}
|
||||
}
|
||||
|
||||
/* 0x0013CDF0: read status; 0 = idle (with 1: poll, with 0: wait). */
|
||||
s32 uyafn_13CDF0(s32 block) { (void)block; uya_cd_service(); return 0; }
|
||||
|
||||
/* 0x0013CF40: set the read-completion callback, returns the previous one. */
|
||||
void *uyafn_13CF40(void (*cb)(s32)) { void *old = (void *)cd_callback; cd_callback = cb; return old; }
|
||||
|
||||
/* 0x0013CEF8: last read error; 0 = none. */
|
||||
s32 uyafn_13CEF8(void) { return 0; }
|
||||
|
||||
/* 0x0013D290: synchronous read. */
|
||||
s32 uyafn_13D290(s32 lsn, s32 count, void *buf)
|
||||
{
|
||||
read_sectors((u32)lsn, (u32)count, buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 0x0013D2E0: read the table of contents into 0x160C40. */
|
||||
s32 uyafn_13D2E0(void)
|
||||
{
|
||||
return uyafn_13D290(0x3E9, 0x10, (void *)0x160C40);
|
||||
}
|
||||
|
||||
void uya_disc_boot(void)
|
||||
{
|
||||
uyafn_13D2E0();
|
||||
uya_log("table of contents loaded at 0x00160C40");
|
||||
}
|
||||
|
||||
/* 0x0013D310: load the next level's part of the table of contents (boot_elf).
|
||||
* Per level there are three disc locations (TOC + 0x7400/0x7408/0x7410 +
|
||||
* level * 0x18); each is read into `buf` and copied into place: 0x60 bytes to
|
||||
* TOC + 0x79A0, 0x1818 bytes to TOC + 0x7A00 and 0x26F0 bytes to 0x00169E58. */
|
||||
s32 uyafn_13D310(s32 level, u8 *buf, s32 x)
|
||||
{
|
||||
u8 *toc = (u8 *)0x00160C40;
|
||||
u8 *e = toc + level * 0x18;
|
||||
(void)x;
|
||||
if (uya_disc_read(*(u32 *)(e + 0x7408), 1, buf) == 0) memcpy(toc + 0x79A0, buf, 0x60);
|
||||
if (uya_disc_read(*(u32 *)(e + 0x7400), 4, buf) == 0) memcpy(toc + 0x7A00, buf, 0x1818);
|
||||
if (uya_disc_read(*(u32 *)(e + 0x7410), 5, buf) == 0) memcpy((u8 *)0x00169E58, buf, 0x26F0);
|
||||
uya_log("level %d: table of contents loaded", level);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
#include <stdio.h>
|
||||
/* libdma and the EE DMA controller, as far as the menu needs them.
|
||||
*
|
||||
* sceDmaSend walks the source chain (the DMAtag list) the menu built and
|
||||
* hands each block of data to uya_dma_sink(); the GS renderer will take its
|
||||
* packets from there. Tags with the IRQ bit, and the end of the chain, raise
|
||||
* the channel's DMAC interrupt: the CHCR TAG field holds the last tag's
|
||||
* upper 16 bits, the way the menu's handlers expect. */
|
||||
#include "uya_host.h"
|
||||
#include "uya_runtime.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* EE DMA channel register blocks: CHCR at +0x00, MADR +0x10, QWC +0x20, TADR +0x30. */
|
||||
static const u32 chan_base[10] = {
|
||||
0x10008000, 0x10009000, 0x1000A000, 0x1000B000, 0x1000B400,
|
||||
0x1000C000, 0x1000C400, 0x1000C800, 0x1000D000, 0x1000D400,
|
||||
};
|
||||
|
||||
/* ---- DMAC interrupt handlers (kernel AddDmacHandler / EnableDmac) ---- */
|
||||
typedef s32 (*dmac_handler_t)(s32 ch);
|
||||
static dmac_handler_t handlers[16];
|
||||
static u32 enabled;
|
||||
|
||||
/* 0x0011EB30: AddDmacHandler(channel, handler, next): returns a handler id. */
|
||||
s32 uyafn_11EB30(s32 ch, dmac_handler_t h, s32 next)
|
||||
{
|
||||
(void)next;
|
||||
if (ch >= 0 && ch < 16) handlers[ch] = h;
|
||||
return ch + 1;
|
||||
}
|
||||
|
||||
/* 0x0011F9A8: EnableDmac(channel) */
|
||||
s32 uyafn_11F9A8(s32 ch) { if (ch >= 0 && ch < 16) enabled |= 1u << ch; return 0; }
|
||||
|
||||
static int walking[10]; /* a chain walk is running on the channel (its IRQ handler may set STR) */
|
||||
static int irq_handled(s32 ch) { return ch >= 0 && ch < 16 && (enabled & (1u << ch)) && handlers[ch]; }
|
||||
|
||||
/* RemoveDmacHandler / DisableDmac */
|
||||
s32 uya_dmac_remove(s32 ch) { if (ch >= 0 && ch < 16) handlers[ch] = 0; return 0; }
|
||||
s32 uya_dmac_disable(s32 ch) { if (ch >= 0 && ch < 16) enabled &= ~(1u << ch); return 0; }
|
||||
|
||||
static void raise_irq(s32 ch)
|
||||
{
|
||||
if (ch >= 0 && ch < 16 && (enabled & (1u << ch)) && handlers[ch]) handlers[ch](ch);
|
||||
}
|
||||
|
||||
/* 0x0012D650: sceDmaGetChan(id) */
|
||||
void *uyafn_12D650(s32 id)
|
||||
{
|
||||
if (id < 0 || id > 9) return 0;
|
||||
return (void *)(uintptr_t)chan_base[id];
|
||||
}
|
||||
|
||||
static u32 zero_qw[4 * 4096];
|
||||
static void *phys(u32 a)
|
||||
{
|
||||
/* host memory (the recompiled code's stack, host buffers): as is */
|
||||
if (a >= 0x40000000u && (a >> 28) != 7 && (a >> 31) == 0) return (void *)(uintptr_t)a;
|
||||
if (a & 0x80000000u) return (void *)(uintptr_t)(PS2_SPR + (a & 0x3FF0)); /* scratchpad */
|
||||
a &= 0x01FFFFF0u;
|
||||
if (a < PS2_RAM_LO) { /* kernel area: never valid for the game; read as zeros */
|
||||
static int warned;
|
||||
if (!warned++) uya_log("DMA: chain points into the kernel area (0x%08X); reading zeros", a);
|
||||
return zero_qw;
|
||||
}
|
||||
return (void *)(uintptr_t)a;
|
||||
}
|
||||
|
||||
unsigned long long nsends, nqw;
|
||||
|
||||
/* Where transferred data goes: VIF1 data to the VIF decoder (host/src/gfx/vif.c). */
|
||||
void uya_vif1_data(const u32 *data, u32 qwc);
|
||||
void uya_vif1_words(const u32 *data, u32 nwords);
|
||||
void uya_gs_gif(const u32 *d, u32 qwc);
|
||||
void uya_vif0_data(const u32 *data, u32 qwc);
|
||||
void uya_vif0_words(const u32 *data, u32 nwords);
|
||||
static void uya_dma_sink(s32 ch, const void *data, u32 qwc)
|
||||
{
|
||||
nqw += qwc;
|
||||
if (ch == 0) { uya_vif0_data(data, qwc); return; }
|
||||
if (ch == 1) uya_vif1_data(data, qwc);
|
||||
if (ch == 2) { extern const char *uya_gs_src; uya_gs_src = "PATH3 (DMA ch2)"; uya_gs_gif(data, qwc); } /* GIF channel: PATH3 */
|
||||
}
|
||||
|
||||
static s32 chan_index(void *chan)
|
||||
{
|
||||
for (s32 i = 0; i < 10; i++) if ((u32)(uintptr_t)chan == chan_base[i]) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 0x0012D938: sceDmaSend(chan, tag): start a source-chain transfer from `tag`. */
|
||||
s32 uyafn_12D938(void *chan, void *tag)
|
||||
{
|
||||
s32 ch = chan_index(chan);
|
||||
volatile u32 *regs = (volatile u32 *)chan;
|
||||
u32 t = (u32)(uintptr_t)tag, stack[2], sp = 0;
|
||||
u32 tte = regs[0] & 0x40; /* CHCR TTE as the game set it */
|
||||
nsends++;
|
||||
if (nsends <= 4 || (nsends & (nsends - 1)) == 0)
|
||||
uya_log("DMA send #%llu on channel %d, chain at 0x%08X (%llu qwords so far)", nsends, ch, t, nqw);
|
||||
for (int guard = 0; guard < 1000000; guard++) {
|
||||
u32 *q = phys(t);
|
||||
u32 lo = q[0], hi = q[1];
|
||||
u32 qwc = lo & 0xFFFF, id = (lo >> 28) & 7, irq = lo >> 31, addr = hi & 0x7FFFFFFF;
|
||||
u32 next = 0;
|
||||
int end = 0;
|
||||
{
|
||||
static long long dlog = -2;
|
||||
if (dlog == -2) { const char *e = getenv("UYA_DMALOG"); dlog = e ? atoll(e) : -1; }
|
||||
extern int uya_vif_log;
|
||||
uya_vif_log = (long long)nsends == dlog;
|
||||
if (uya_vif_log) uya_log(" DMA tag at 0x%08X: id %u qwc %u addr 0x%08X upper %08X %08X", t, id, qwc, addr, q[2], q[3]);
|
||||
}
|
||||
/* Tag transfer (CHCR TTE): the tag's upper 64 bits go to the VIF first
|
||||
* (two VIF codes). The menu's VIF1 chains always carry VIF codes there
|
||||
* (NOP, DIRECT), so it is on for channel 1. */
|
||||
if (ch == 1 && tte) uya_vif1_words(q + 2, 2);
|
||||
switch (id) {
|
||||
case 0: uya_dma_sink(ch, phys(addr), qwc); end = 1; break; /* refe */
|
||||
case 1: uya_dma_sink(ch, q + 4, qwc); next = t + 16 + qwc * 16; break; /* cnt */
|
||||
case 2: uya_dma_sink(ch, q + 4, qwc); next = addr; break; /* next */
|
||||
case 3: case 4: uya_dma_sink(ch, phys(addr), qwc); next = t + 16; break; /* ref, refs */
|
||||
case 5: uya_dma_sink(ch, q + 4, qwc); /* call */
|
||||
if (sp < 2) stack[sp++] = t + 16 + qwc * 16;
|
||||
next = addr; break;
|
||||
case 6: uya_dma_sink(ch, q + 4, qwc); /* ret */
|
||||
if (sp) next = stack[--sp]; else end = 1;
|
||||
break;
|
||||
case 7: uya_dma_sink(ch, q + 4, qwc); end = 1; break; /* end */
|
||||
}
|
||||
if (irq || end) {
|
||||
u32 tie = regs[0] & 0x80;
|
||||
regs[0] = (lo & 0xFFFF0000u) | (regs[0] & 0xC1) | 0x4 | 0x100; /* CHCR: TAG, chain mode, STR */
|
||||
regs[12] = next ? next : t; /* TADR: the handlers ignore a zero tag address */
|
||||
if (irq && !end && tie && irq_handled(ch)) {
|
||||
/* CHCR TIE: the DMAC stops at a tag with the IRQ bit; the
|
||||
* channel's handler (RAC1's VU1 chain handler) restarts it
|
||||
* by setting STR again. Without that the chain stays
|
||||
* suspended until the game sets STR. */
|
||||
regs[0] &= ~0x100u;
|
||||
walking[ch] = 1;
|
||||
raise_irq(ch);
|
||||
walking[ch] = 0;
|
||||
if (!(regs[0] & 0x100)) { regs[8] = 0; return 0; }
|
||||
t = regs[12];
|
||||
continue;
|
||||
}
|
||||
raise_irq(ch);
|
||||
}
|
||||
if (end) break;
|
||||
t = next;
|
||||
}
|
||||
if (ch == 1) {
|
||||
void uya_gfx_frame_stats(void); void uya_gif_stats(void);
|
||||
static long long stop_after = -2;
|
||||
if (stop_after == -2) { const char *e = getenv("UYA_FRAMES"); stop_after = e ? atoll(e) : -1; }
|
||||
uya_gfx_frame_stats();
|
||||
if (nsends == 600) uya_gif_stats();
|
||||
if (stop_after > 0 && (long long)nsends >= stop_after) {
|
||||
void uya_gs_stats(void);
|
||||
const char *rd = getenv("UYA_RAMDUMP"); /* testing: save PS2 RAM (0x100000-0x2000000) */
|
||||
void vu_stats(void);
|
||||
uya_log("UYA_FRAMES=%lld reached", stop_after); uya_gif_stats(); uya_gs_stats(); vu_stats();
|
||||
if (rd) { FILE *f = fopen(rd, "wb"); if (f) { fwrite((void *)0x100000, 1, 0x1F00000, f); fclose(f); } }
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
regs[0] &= ~0x100u;
|
||||
regs[8] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* RAC1 starts DMA by writing a channel's registers itself (CHCR with STR,
|
||||
* bit 8), not only through sceDmaSend. Those registers are plain memory on
|
||||
* the host, so uya_dma_poll() runs any channel whose STR bit is set: chain
|
||||
* mode from TADR through the same walker as sceDmaSend, normal mode as one
|
||||
* block of QWC quadwords at MADR. Channel 0 (VIF0) carries VU0 microcode
|
||||
* uploads and data to the VIF0 decoder (host/src/gfx/vif.c). Called from the game's busy-wait
|
||||
* helper, at every vsync and from the GS sync calls. */
|
||||
void uya_dma_kick(s32 ch);
|
||||
void uya_dma_poll(void)
|
||||
{
|
||||
static int busy;
|
||||
if (busy) return;
|
||||
busy = 1;
|
||||
for (s32 ch = 0; ch < 10; ch++) uya_dma_kick(ch);
|
||||
busy = 0;
|
||||
}
|
||||
|
||||
/* Scratchpad channels: 8 (fromSPR: SPR -> RAM) and 9 (toSPR: RAM -> SPR), normal mode;
|
||||
* SADR at +0x80 is the scratchpad offset. */
|
||||
static void spr_transfer(s32 ch)
|
||||
{
|
||||
volatile u32 *r = (volatile u32 *)(uintptr_t)chan_base[ch];
|
||||
u32 qwc = r[8] & 0xFFFF, madr = r[4], sadr = r[32] & 0x3FF0;
|
||||
u8 *spr = (u8 *)(uintptr_t)PS2_SPR, *ram = phys(madr);
|
||||
for (u32 i = 0; i < qwc; i++) {
|
||||
u8 *s = spr + ((sadr + 16 * i) & 0x3FF0), *m = ram + 16 * i;
|
||||
if (ch == 9) memcpy(s, m, 16); else memcpy(m, s, 16);
|
||||
}
|
||||
r[4] = madr + qwc * 16; r[32] = (sadr + qwc * 16) & 0x3FF0; r[8] = 0;
|
||||
}
|
||||
|
||||
/* run channel ch now if its STR bit is set */
|
||||
void uya_dma_kick(s32 ch)
|
||||
{
|
||||
volatile u32 *r = (volatile u32 *)(uintptr_t)chan_base[ch];
|
||||
u32 chcr = r[0];
|
||||
if (!(chcr & 0x100)) return;
|
||||
if (ch < 10 && walking[ch]) return; /* restarted from its own IRQ handler: the walk goes on */
|
||||
u32 mode = (chcr >> 2) & 3;
|
||||
if (ch == 1 || ch == 2) {
|
||||
if (mode == 1) { uyafn_12D938((void *)(uintptr_t)chan_base[ch], (void *)(uintptr_t)r[12]); return; }
|
||||
if (r[8]) uya_dma_sink(ch, phys(r[4]), r[8] & 0xFFFF);
|
||||
} else if (ch == 0) { /* VIF0: VU0 microcode (MPG) and data */
|
||||
if (mode == 1) {
|
||||
u32 t = r[12], stack[2], sp = 0;
|
||||
for (int g = 0; g < 65536; g++) {
|
||||
u32 *q = phys(t); u32 qwc = q[0] & 0xFFFF, id = (q[0] >> 28) & 7, addr = q[1] & 0x7FFFFFFF, next = 0;
|
||||
int end = 0;
|
||||
if (chcr & 0x40) uya_vif0_words(q + 2, 2);
|
||||
switch (id) {
|
||||
case 0: uya_dma_sink(0, phys(addr), qwc); end = 1; break;
|
||||
case 1: uya_dma_sink(0, q + 4, qwc); next = t + 16 + qwc * 16; break;
|
||||
case 2: uya_dma_sink(0, q + 4, qwc); next = addr; break;
|
||||
case 3: case 4: uya_dma_sink(0, phys(addr), qwc); next = t + 16; break;
|
||||
case 5: uya_dma_sink(0, q + 4, qwc); if (sp < 2) stack[sp++] = t + 16 + qwc * 16; next = addr; break;
|
||||
case 6: uya_dma_sink(0, q + 4, qwc); if (sp) next = stack[--sp]; else end = 1; break;
|
||||
default: uya_dma_sink(0, q + 4, qwc); end = 1; break;
|
||||
}
|
||||
if (end) break;
|
||||
t = next;
|
||||
}
|
||||
} else if (r[8]) uya_dma_sink(0, phys(r[4]), r[8] & 0xFFFF);
|
||||
} else if (ch == 8 || ch == 9) {
|
||||
if (mode == 0) spr_transfer(ch);
|
||||
else if (mode == 1) { /* chain mode toSPR: walk tags from TADR into the scratchpad */
|
||||
u32 t = r[12], sadr = r[32] & 0x3FF0;
|
||||
for (int g = 0; g < 4096; g++) {
|
||||
u32 *q = phys(t); u32 qwc = q[0] & 0xFFFF, id = (q[0] >> 28) & 7, addr = q[1] & 0x7FFFFFFF;
|
||||
const u8 *src = (id == 1 || id == 7) ? (u8 *)(q + 4) : (u8 *)phys(addr);
|
||||
for (u32 i = 0; i < qwc; i++) memcpy((u8 *)(uintptr_t)PS2_SPR + ((sadr + 16 * i) & 0x3FF0), src + 16 * i, 16);
|
||||
sadr = (sadr + 16 * qwc) & 0x3FF0;
|
||||
if (id == 7 || id == 0) break;
|
||||
t = id == 1 ? t + 16 + qwc * 16 : (id == 2 ? addr : t + 16);
|
||||
}
|
||||
r[32] = sadr;
|
||||
}
|
||||
}
|
||||
r[0] &= ~0x100u;
|
||||
r[8] = 0;
|
||||
}
|
||||
|
||||
void rc_io_write32(u32 a, u32 v)
|
||||
{
|
||||
*(volatile u32 *)(uintptr_t)a = v;
|
||||
for (s32 ch = 0; ch < 10; ch++)
|
||||
if (a == chan_base[ch] && (v & 0x100)) { uya_dma_kick(ch); return; }
|
||||
}
|
||||
|
||||
typedef union { u64 d[2]; u32 w[4]; } R128h;
|
||||
void rc_io_write128(u32 a, const void *v)
|
||||
{
|
||||
if (a == 0x10005000u) { uya_vif1_data((const u32 *)v, 1); return; } /* VIF1 FIFO */
|
||||
if (a == 0x10006000u) { extern const char *uya_gs_src; uya_gs_src = "PATH3 (FIFO)"; uya_gs_gif((const u32 *)v, 1); return; } /* GIF FIFO (PATH3) */
|
||||
if (a == 0x10004000u) { uya_vif0_data((const u32 *)v, 1); return; } /* VIF0 FIFO */
|
||||
const u32 *w = v;
|
||||
for (int i = 0; i < 4; i++) rc_io_write32(a + 4 * i, w[i]);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/* libgraph: vsync and the EE timer the menu measures frames with. */
|
||||
#include "uya_host.h"
|
||||
#include "uya_runtime.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#define T0_COUNT (*(volatile u32 *)0x10000800) /* EE timer 0, BUSCLK/256 = 576000 Hz */
|
||||
/* the length of a field: NTSC 59.94 Hz until the game sets the video mode
|
||||
* (sceGsResetGraph, host/src/sdk/gsdk.c); RAC1 (PAL) runs at 50 Hz */
|
||||
static uint32_t frame_us = 16683;
|
||||
void uya_set_video_pal(int pal) { frame_us = pal ? 20000 : 16683; }
|
||||
#define FRAME_US frame_us
|
||||
uint32_t uya_frame_us(void) { return frame_us; }
|
||||
|
||||
static uint64_t last, next_vsync;
|
||||
static u32 field;
|
||||
|
||||
/* 0x0012C908: sceGsSyncV(mode): wait for the next vertical blank, return the field (0/1).
|
||||
* Paces the game at the video mode's field rate (50 Hz PAL, 59.94 Hz NTSC) and
|
||||
* advances timer 0 by the time that really passed. */
|
||||
void uya_gs_vsync(void);
|
||||
void uya_sound_pump(void);
|
||||
void uya_mc_trace(void);
|
||||
void uya_call_vsync_callback(void);
|
||||
void uya_dma_poll(void);
|
||||
void rac1_cd_service(void);
|
||||
void rac1_test_hooks(void);
|
||||
static uint32_t vsyncs;
|
||||
uint32_t uya_frame_count(void) { return vsyncs; }
|
||||
/* testing: UYA_FIXEDTIME=1 puts the game on a clock that moves a field per
|
||||
* vsync (timer 0 here, the Linux sound output's pacing) and never waits, so a
|
||||
* run does the same whatever the speed of the machine (frames can be compared
|
||||
* between builds) */
|
||||
static int fixed_time = -1;
|
||||
static uint64_t vclock;
|
||||
uint64_t uya_fixed_clock(void) { return fixed_time > 0 ? vclock : 0; }
|
||||
|
||||
s32 uyafn_12C908(s32 mode)
|
||||
{
|
||||
vsyncs++;
|
||||
if (fixed_time < 0) fixed_time = getenv("UYA_FIXEDTIME") != 0;
|
||||
uint64_t now = fixed_time ? (vclock += FRAME_US) : plat_time_us();
|
||||
(void)mode;
|
||||
if (!next_vsync) { next_vsync = now + FRAME_US; last = now; }
|
||||
if (now < next_vsync && !fixed_time) { plat_sleep_us(next_vsync - now); now = plat_time_us(); }
|
||||
next_vsync += FRAME_US;
|
||||
if (now > next_vsync + 4 * FRAME_US) next_vsync = now + FRAME_US; /* fell far behind: resync */
|
||||
T0_COUNT += (u32)((now - last) * 576000u / 1000000u);
|
||||
last = now;
|
||||
field ^= 1;
|
||||
uya_gs_vsync();
|
||||
uya_sound_pump();
|
||||
uya_mc_trace();
|
||||
uya_dma_poll();
|
||||
rac1_cd_service();
|
||||
uya_call_vsync_callback();
|
||||
rac1_test_hooks();
|
||||
plat_poll();
|
||||
return (s32)field;
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/* libgraph (Sony's GS library) functions the menu calls, plus boot_elf's
|
||||
* game-state word. Ported from the decomp's boot_elf sources and assembly
|
||||
* (src/boot_elf/core/12A948.c, asm/boot_elf/nonmatchings/core). */
|
||||
#include "uya_host.h"
|
||||
#include "uya_runtime.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
void uya_gs_gif(const u32 *d, u32 qwc);
|
||||
|
||||
typedef struct { s16 inter, omode, ffmode, version; } GParam;
|
||||
|
||||
/* 0x0012C078: sceGsGetGParam(); the table is boot_elf data (interlace, NTSC, frame mode). */
|
||||
/* RAC1: libgraph's GParam is D_00132E40 (func_00121D08). */
|
||||
void *uyafn_12C078(void) { return (void *)0x00132E40; }
|
||||
|
||||
/* 0x0012C138: sceGsSetDefDispEnv(disp, psm, w, h, dx, dy) for NTSC and PAL */
|
||||
void uyafn_12C138(u64 *disp, s32 psm, s32 w, s32 h, s32 dx, s32 dy)
|
||||
{
|
||||
GParam *gp = uyafn_12C078();
|
||||
s16 sw = (s16)w, sh = (s16)h;
|
||||
disp[0] = 0x66; /* PMODE: circuit 2, ALP 0 */
|
||||
disp[1] = gp->inter == 0 ? 2 : (gp->ffmode ? 3 : 1); /* SMODE2 */
|
||||
disp[2] = ((u64)(psm & 0xF) << 15) | ((u64)(((sw + 63) >> 6) & 0x3F) << 9); /* DISPFB */
|
||||
if ((gp->omode == 2 || gp->omode == 3) && sw > 0) {
|
||||
s32 ntsc = gp->omode == 2;
|
||||
s32 magh = (sw + 0x9FF) / sw;
|
||||
u64 x = (u64)((dx * magh + (ntsc ? 0x27C : 0x290)) & 0xFFF);
|
||||
u64 y = (u64)(((s16)dy + (ntsc ? 0x32 : 0x48)) & 0xFFF);
|
||||
u64 dh = (u64)((gp->inter == 1 && gp->ffmode) ? sh * 2 - 1 : sh - 1);
|
||||
if (gp->inter != 1) y = (u64)(((s16)dy + (ntsc ? 0x19 : 0x24)) & 0xFFF), dh = (u64)(sh - 1);
|
||||
disp[3] = x | (y << 12) | ((u64)(magh - 1) << 23) | ((u64)(magh * sw - 1) << 32) | (dh << 44);
|
||||
} else if (gp->omode == 0x50 && sw > 0) { /* DTV 480p (progressive scan) */
|
||||
u64 x = (u64)((((0x2D0 - sw) / 2) * 2 + dx * 2 + 0xE8) & 0xFFF);
|
||||
u64 y = (u64)(((s16)dy + 0x23) & 0xFFF);
|
||||
disp[3] = x | (y << 12) | (1ull << 23) | ((u64)(sw * 2 - 1) << 32) | ((u64)(sh - 1) << 44);
|
||||
} else {
|
||||
disp[3] = 0;
|
||||
}
|
||||
disp[4] = 0; /* BGCOLOR */
|
||||
}
|
||||
|
||||
/* 0x0012C4B0: sceGsPutDispEnv(disp) */
|
||||
void uyafn_12C4B0(u64 *disp)
|
||||
{
|
||||
GParam *gp = uyafn_12C078();
|
||||
if (!disp) return;
|
||||
if (gp->version == 1) {
|
||||
*(volatile u64 *)0x12000000 = disp[0];
|
||||
*(volatile u64 *)0x12000070 = disp[2];
|
||||
*(volatile u64 *)0x12000080 = disp[3];
|
||||
*(volatile u64 *)0x120000C0 = disp[4];
|
||||
} else {
|
||||
*(volatile u64 *)0x12000000 = disp[0];
|
||||
*(volatile u64 *)0x12000020 = disp[1];
|
||||
*(volatile u64 *)0x12000090 = disp[2];
|
||||
*(volatile u64 *)0x120000A0 = disp[3];
|
||||
*(volatile u64 *)0x120000E0 = disp[4];
|
||||
}
|
||||
}
|
||||
|
||||
/* 0x0012C570: size of a frame buffer in pages */
|
||||
s16 uyafn_12C570(s16 psm, s16 w, s16 h)
|
||||
{
|
||||
GParam *gp = uyafn_12C078();
|
||||
s32 bw = (w + 63) / 64, bh = (psm & 2) ? (h + 63) / 64 : (h + 31) / 32;
|
||||
if (gp->inter == 1 && gp->ffmode == 0) return (s16)(bw * bh);
|
||||
return (s16)(bw * bh * 2);
|
||||
}
|
||||
|
||||
/* 0x0012C638: sceGsSetDefDrawEnv(draw, psm, w, h, ztest, zpsm): 8 A+D pairs */
|
||||
s32 uyafn_12C638(u64 *d, s16 psm, s16 w, s16 h, s16 ztest, s16 zpsm)
|
||||
{
|
||||
d[1] = 0x4C; d[0] = ((u64)(((w + 63) >> 6) & 0x3F) << 16) | ((u64)(psm & 0xF) << 24);
|
||||
d[3] = 0x4E; d[2] = (u64)(u16)uyafn_12C570(psm, w, h) | ((u64)(zpsm & 0xF) << 24) | (ztest == 0 ? 1ull << 32 : 0);
|
||||
d[5] = 0x18; d[4] = ((u64)(0x800 - (s16)(w >> 1)) << 4) | ((u64)(0x800 - (s16)(h >> 1)) << 36);
|
||||
d[7] = 0x40; d[6] = ((u64)(w - 1) << 16) | ((u64)(h - 1) << 48);
|
||||
d[9] = 0x1A; d[8] = 1;
|
||||
d[11] = 0x46; d[10] = 1;
|
||||
d[13] = 0x45; d[12] = (psm & 2) ? 1 : 0;
|
||||
d[15] = 0x47; d[14] = ztest ? (((u64)(ztest & 3) << 17) | 0x10000) : 0x30000;
|
||||
return 8;
|
||||
}
|
||||
|
||||
/* 0x0012C9A0: sceGsSetDefLoadImage(lp, dbp, dbw, dpsm, x, y, w, h): a 6-qword
|
||||
* GIF packet (BITBLTBUF, TRXPOS, TRXREG, TRXDIR, then an IMAGE tag). */
|
||||
s32 uyafn_12C9A0(u64 *lp, s32 dbp, s32 dbw, s32 dpsm, s32 x, s32 y, s32 w, s32 h)
|
||||
{
|
||||
s16 p = (s16)dpsm, W = (s16)w, H = (s16)h;
|
||||
s32 qwc;
|
||||
/* libgraph's table (RAC1 0x001531E0): PSMT8H and PSMT4HL/HH are 8- and
|
||||
* 4-bit transfers too (the HUD's PSMT8H textures, re-sent when the pause
|
||||
* menu closes); a format it does not know sends no image */
|
||||
switch (p) {
|
||||
case 0x00: case 0x30: qwc = W * H / 4; break;
|
||||
case 0x01: case 0x31: qwc = W * H * 3 / 16; break;
|
||||
case 0x02: case 0x0A: case 0x32: case 0x3A: qwc = W * H / 8; break;
|
||||
case 0x13: case 0x1B: qwc = W * H / 16; break;
|
||||
case 0x14: case 0x24: case 0x2C: qwc = W * H / 32; break;
|
||||
default: qwc = 0; break;
|
||||
}
|
||||
if (qwc > 0x7FFF) { uya_log("sceGsSetDefLoadImage: image too large"); return 0; }
|
||||
lp[0] = 4 | 0x8000 | (1ull << 60); lp[1] = 0xE;
|
||||
lp[2] = ((u64)(u16)(s16)dbp << 32) | ((u64)((s16)dbw & 0x3F) << 48) | ((u64)(p & 0x3F) << 56); lp[3] = 0x50;
|
||||
lp[4] = ((u64)((s16)x & 0x7FF) << 32) | ((u64)((s16)y & 0x7FF) << 48); lp[5] = 0x51;
|
||||
lp[6] = (u64)(W & 0xFFF) | ((u64)(H & 0xFFF) << 32); lp[7] = 0x52;
|
||||
lp[8] = 0; lp[9] = 0x53;
|
||||
lp[10] = (u64)(qwc & 0x7FFF) | 0x8000 | (2ull << 58); lp[11] = 0;
|
||||
return 6;
|
||||
}
|
||||
|
||||
static const u32 *ps2_ptr(const void *p)
|
||||
{
|
||||
u32 a = (u32)(uintptr_t)p;
|
||||
/* PS2 uncached / accelerated mirrors of main RAM; anything else (main RAM,
|
||||
* the scratchpad, or a host pointer such as a stack buffer) is used as is */
|
||||
if (a >= 0x20000000 && a < 0x40000000) return (const u32 *)(uintptr_t)(a & 0x0FFFFFFF);
|
||||
return (const u32 *)p;
|
||||
}
|
||||
|
||||
/* 0x0012CCC8: sceGsExecLoadImage(lp, src): sends the packet then the pixels over GIF channel 2 */
|
||||
s32 uyafn_12CCC8(u64 *lp, void *src)
|
||||
{
|
||||
{ extern const char *uya_gs_src; uya_gs_src = "PATH3 (GS library)"; }
|
||||
uya_gs_gif(ps2_ptr(lp), 6);
|
||||
uya_gs_gif(ps2_ptr(src), (u32)(lp[10] & 0x7FFF));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 0x0013D3C0 / 0x0013D3C8: set / get a game-state word in boot_elf (gp-relative).
|
||||
* The menu's front loop uses it for its own states (0 = title, 5 = attract demo...). */
|
||||
static s32 game_state;
|
||||
void uyafn_13D3C0(s32 v) { game_state = v; }
|
||||
s32 uyafn_13D3C8(void) { return game_state; }
|
||||
|
||||
/* 0x0012BEE8: sceGsResetGraph(mode, inter, omode, ffmode). Mode 0 resets the
|
||||
* GS and sets the video mode, mode 5 only sets the mode, mode 1 only resets.
|
||||
* The host keeps the mode in the GParam table (sceGsGetGParam) and in SMODE2,
|
||||
* which is what the display code reads; there is no CRT to program. */
|
||||
void uyafn_12BEE8(s32 mode, s32 inter, s32 omode, s32 ffmode)
|
||||
{
|
||||
GParam *gp = uyafn_12C078();
|
||||
if (mode != 0 && mode != 5) return;
|
||||
gp->inter = (s16)inter;
|
||||
gp->omode = (s16)omode;
|
||||
gp->ffmode = ffmode != 0;
|
||||
*(volatile u64 *)0x12000020 = (u64)((inter & 1) | ((ffmode & 1) << 1));
|
||||
{ void uya_set_video_pal(int pal); uya_set_video_pal((omode & 0xFF) == 3); } /* vsync pacing (graph.c) */
|
||||
uya_log("video mode: %s, %s%s", (omode & 0xFF) == 0x50 ? "480p" : (omode & 0xFF) == 3 ? "PAL" : (omode & 0xFF) == 2 ? "NTSC" : "other",
|
||||
inter & 1 ? "interlaced" : "non-interlaced", ffmode & 1 ? " frame mode" : "");
|
||||
}
|
||||
|
||||
/* 0x0012A950: sceGsResetPath(): resets VIF1, VU1 and the GIF. Nothing to reset here. */
|
||||
void uyafn_12A950(void) {}
|
||||
|
||||
/* 0x0012D4D8: sceGsSyncVCallback(func): installs a vblank handler and returns
|
||||
* the previous one. The menu passes boot_elf's handler at 0x0013D208, which
|
||||
* only feeds boot_elf's own timing; the host keeps time itself, so the handler
|
||||
* is recorded and not called. */
|
||||
/* RAC1: the game's handler (vsync_callback, 0x0012F308) counts frames and
|
||||
* reads timer 0, so it is called at every vsync (host/src/sdk/graph.c). */
|
||||
static s32 vsync_cb;
|
||||
s32 uyafn_12D4D8(s32 fn) { s32 old = vsync_cb; vsync_cb = fn; return old; }
|
||||
void uya_call_vsync_callback(void)
|
||||
{
|
||||
if (vsync_cb) { typedef s32 (*cb_t)(s32); ((cb_t)(uintptr_t)(u32)vsync_cb)(0); }
|
||||
}
|
||||
|
||||
/* 0x0012A9F0: sceGsSyncPath(mode, timeout): waits for DMA, VIF1 and GIF to go
|
||||
* idle and returns 0. Host transfers finish when they are sent, so the paths
|
||||
* are always idle. */
|
||||
s32 uyafn_12A9F0(s32 mode, s32 timeout) { (void)mode; (void)timeout; return 0; }
|
||||
@@ -0,0 +1,124 @@
|
||||
/* Engine core (boot_elf) C library functions the menu calls, implemented on the host.
|
||||
* Each is named by its PS2 address so the menu's calls and function pointers reach it. */
|
||||
#include "uya_host.h"
|
||||
|
||||
/* 0x0011A264: memset(dst, c, n) (newlib) */
|
||||
void *uyafn_11A264(void *dst, s32 c, u32 n)
|
||||
{
|
||||
u8 *p = dst;
|
||||
while (n--) *p++ = (u8)c;
|
||||
return dst;
|
||||
}
|
||||
|
||||
#include <stdarg.h>
|
||||
int vprintf(const char *, va_list);
|
||||
|
||||
/* 0x00121760: printf(fmt, ...) */
|
||||
int uyafn_121760(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int n;
|
||||
va_start(ap, fmt);
|
||||
n = vprintf(fmt, ap);
|
||||
va_end(ap);
|
||||
return n;
|
||||
}
|
||||
|
||||
/* 0x0011F0A0: FlushCache(mode). Nothing to do on x86. */
|
||||
s32 uyafn_11F0A0(s32 mode) { (void)mode; return 0; }
|
||||
|
||||
void uya_log(const char *fmt, ...);
|
||||
/* 0x00116F98: __assert(file, line, expression) (newlib). Logged; the game carries on. */
|
||||
void uyafn_116F98(const char *file, s32 line, const char *expr)
|
||||
{
|
||||
uya_log("ASSERT %s:%d: %s", file ? file : "?", line, expr ? expr : "?");
|
||||
}
|
||||
|
||||
int vsprintf(char *, const char *, va_list);
|
||||
/* 0x0011B2E8: sprintf(buf, fmt, ...) */
|
||||
int uyafn_11B2E8(char *buf, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int n;
|
||||
va_start(ap, fmt);
|
||||
n = vsprintf(buf, fmt, ap);
|
||||
va_end(ap);
|
||||
return n;
|
||||
}
|
||||
|
||||
/* 0x00116FD0: atexit(fn). The menu registers cleanup it never needs here. */
|
||||
s32 uyafn_116FD0(void (*fn)(void)) { (void)fn; return 0; }
|
||||
|
||||
/* 0x0011BB58: strncpy (newlib, an MMI-optimised version on the EE). */
|
||||
char *uyafn_11BB58(char *dst, const char *src, u32 n)
|
||||
{
|
||||
u32 i = 0;
|
||||
for (; i < n && src[i]; i++) dst[i] = src[i];
|
||||
for (; i < n; i++) dst[i] = 0;
|
||||
return dst;
|
||||
}
|
||||
|
||||
/* 0x0013D3E0: the game's rand(): seed (0x001D4D80) = seed * 0x343FD + 0x269EC3,
|
||||
* returns bits 16-30. */
|
||||
s32 uyafn_13D3E0(void)
|
||||
{
|
||||
u32 *seed = (u32 *)0x001D4D80;
|
||||
*seed = *seed * 0x343FDu + 0x269EC3u;
|
||||
return (s32)((*seed >> 16) & 0x7FFF);
|
||||
}
|
||||
|
||||
/* 0x00124ED8: LoadExecPS2(path, argc, argv): the menu starts another
|
||||
* executable (Multiplayer starts cdrom0:\I5BOOTN.ELF, the online game). The
|
||||
* wrapper has only the menu, so it says so and stops. */
|
||||
void uya_log(const char *fmt, ...);
|
||||
void exit(int);
|
||||
void uyafn_124ED8(const char *path, s32 argc, const char **argv)
|
||||
{
|
||||
(void)argc; (void)argv;
|
||||
uya_log("the menu now starts %s (another program on the disc); the wrapper only has the menu, so it stops here.", path ? path : "?");
|
||||
exit(0);
|
||||
}
|
||||
/* 0x0012EF10, 0x00121B38: shut down the IOP link before LoadExecPS2; nothing to do. */
|
||||
void uyafn_12EF10(void) {}
|
||||
void uyafn_121B38(void) {}
|
||||
/* 0x0013B0C8: hand the boot options string to the next program; nothing to do. */
|
||||
void uyafn_13B0C8(void *p) { (void)p; }
|
||||
|
||||
/* 0x0011A0B0: memcpy(dst, src, n) */
|
||||
void *uyafn_11A0B0(void *dst, const void *src, u32 n)
|
||||
{
|
||||
u8 *d = dst; const u8 *s = src;
|
||||
while (n--) *d++ = *s++;
|
||||
return dst;
|
||||
}
|
||||
|
||||
/* 0x0011B868: strlen(s) */
|
||||
u32 uyafn_11B868(const char *s) { u32 n = 0; while (s[n]) n++; return n; }
|
||||
|
||||
/* 0x0011B754: strcpy(dst, src) */
|
||||
char *uyafn_11B754(char *dst, const char *src) { char *d = dst; while ((*d++ = *src++)); return dst; }
|
||||
|
||||
/* 0x0011B610: strcmp(a, b) */
|
||||
s32 uyafn_11B610(const char *a, const char *b)
|
||||
{
|
||||
while (*a && *a == *b) a++, b++;
|
||||
return (u8)*a - (u8)*b;
|
||||
}
|
||||
|
||||
/* 0x0011B9A0: strncmp(a, b, n) */
|
||||
s32 uyafn_11B9A0(const char *a, const char *b, u32 n)
|
||||
{
|
||||
for (; n; n--, a++, b++) { if (*a != *b) return (u8)*a - (u8)*b; if (!*a) return 0; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 0x0011AF48: printf(fmt, ...) through newlib's stdout (another entry to the same output) */
|
||||
int uyafn_11AF48(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int n;
|
||||
va_start(ap, fmt);
|
||||
n = vprintf(fmt, ap);
|
||||
va_end(ap);
|
||||
return n;
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
/* libmc (Sony's memory card library) on the host, backed by folders.
|
||||
*
|
||||
* The menu talks to the card the way every PS2 game does: it starts a command
|
||||
* (sceMcGetInfo, sceMcOpen, sceMcRead, ...) which returns 0 when accepted, then
|
||||
* polls sceMcSync each frame until it reports the command finished, and reads
|
||||
* the command's result from there. Here each command runs at once on a folder
|
||||
* and its result waits for the next sceMcSync.
|
||||
*
|
||||
* Memory card slot 1 (port 0) is the folder "memcard/slot1" next to the
|
||||
* program; slot 2 (port 1) is "memcard/slot2" and only counts as inserted when
|
||||
* that folder exists. Inside a slot folder each save is a folder named like
|
||||
* the card's directory (BASCUS-97353RATCHET), with the save's files in it.
|
||||
*
|
||||
* Result codes are libmc's: 0 success, -1 card changed, -2 unformatted,
|
||||
* -3 card full, -4 no such file or directory (or it already exists, for
|
||||
* mkdir), -5 permission denied, -6 directory not empty, -7 too many open files,
|
||||
* -10 no card. Addresses are named by the boot_elf functions they replace
|
||||
* (src/boot_elf/core/12A948.c). */
|
||||
#include "uya_host.h"
|
||||
#include "uya_runtime.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#define mkdir_one(p) _mkdir(p)
|
||||
#define rmdir_one(p) _rmdir(p)
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#define mkdir_one(p) mkdir(p, 0777)
|
||||
#define rmdir_one(p) rmdir(p)
|
||||
#endif
|
||||
|
||||
#define CARD_KB 8192 /* an 8 MB card, counted in 1 KB clusters */
|
||||
|
||||
static s32 pend_cmd, pend_res; /* the finished command waiting for sceMcSync */
|
||||
static int seen[2]; /* GetInfo reported this card already (else "new card") */
|
||||
|
||||
/* UYA_MCLOG=1 logs card commands (a command repeating its last result, as the
|
||||
* menu's sceMcGetInfo polling does, is logged once) and the card manager trace
|
||||
* below. In PowerShell: $env:UYA_MCLOG=1; in cmd: set UYA_MCLOG=1. */
|
||||
static int mclog(void) { static int v = -1; if (v < 0) v = getenv("UYA_MCLOG") != 0; return v; }
|
||||
|
||||
static void finish(s32 cmd, s32 res)
|
||||
{
|
||||
static s32 last_cmd = -1, last_res = 0x7FFFFFFF;
|
||||
pend_cmd = cmd; pend_res = res;
|
||||
if (mclog() && (cmd != last_cmd || res != last_res))
|
||||
uya_log("memory card: command 0x%X result %d", cmd, res);
|
||||
last_cmd = cmd; last_res = res;
|
||||
}
|
||||
|
||||
static const char *slot_dir(int port)
|
||||
{
|
||||
static char d[2][1024];
|
||||
snprintf(d[port & 1], sizeof d[0], "%s/memcard/slot%d", plat_exe_dir(), (port & 1) + 1);
|
||||
return d[port & 1];
|
||||
}
|
||||
|
||||
static int is_dir(const char *p) { struct stat st; return stat(p, &st) == 0 && S_ISDIR(st.st_mode); }
|
||||
|
||||
/* File size by seeking: st_size from stat() reads as 0 in this 32-bit build. */
|
||||
static long file_size(const char *p)
|
||||
{
|
||||
FILE *f = fopen(p, "rb");
|
||||
long n = 0;
|
||||
if (f) { fseek(f, 0, SEEK_END); n = ftell(f); fclose(f); }
|
||||
return n;
|
||||
}
|
||||
|
||||
static int card_present(int port)
|
||||
{
|
||||
if (port & 1) return is_dir(slot_dir(1));
|
||||
/* slot 1 always has a card: make its folder on first use */
|
||||
char base[1024];
|
||||
snprintf(base, sizeof base, "%s/memcard", plat_exe_dir());
|
||||
mkdir_one(base);
|
||||
mkdir_one(slot_dir(0));
|
||||
return is_dir(slot_dir(0));
|
||||
}
|
||||
|
||||
/* '*' any run of characters, '?' one character */
|
||||
static int glob_match(const char *p, const char *s)
|
||||
{
|
||||
if (!*p) return !*s;
|
||||
if (*p == '*') {
|
||||
for (; ; s++) { if (glob_match(p + 1, s)) return 1; if (!*s) return 0; }
|
||||
}
|
||||
if (!*s) return 0;
|
||||
if (*p == '?' || *p == *s) return glob_match(p + 1, s + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Card path ("/DIR/file" or "DIR/file") to a host path. Wildcards in the
|
||||
* directory part pick the first directory that matches. Returns 0 if a
|
||||
* wildcard directory had no match. */
|
||||
static int host_path(int port, const char *name, char *out, size_t n, char *last, size_t nlast)
|
||||
{
|
||||
char tmp[512], cur[1024];
|
||||
snprintf(cur, sizeof cur, "%s", slot_dir(port));
|
||||
snprintf(tmp, sizeof tmp, "%s", name ? name : "");
|
||||
char *parts[16]; int np = 0;
|
||||
for (char *t = strtok(tmp, "/"); t && np < 16; t = strtok(0, "/")) parts[np++] = t;
|
||||
for (int i = 0; i < np - 1; i++) {
|
||||
if (strpbrk(parts[i], "*?")) {
|
||||
DIR *d = opendir(cur);
|
||||
struct dirent *e; int found = 0;
|
||||
while (d && (e = readdir(d)))
|
||||
if (e->d_name[0] != '.' && glob_match(parts[i], e->d_name)) {
|
||||
size_t l = strlen(cur);
|
||||
snprintf(cur + l, sizeof cur - l, "/%s", e->d_name); found = 1; break;
|
||||
}
|
||||
if (d) closedir(d);
|
||||
if (!found) return 0;
|
||||
} else {
|
||||
size_t l = strlen(cur);
|
||||
snprintf(cur + l, sizeof cur - l, "/%s", parts[i]);
|
||||
}
|
||||
}
|
||||
if (last) snprintf(last, nlast, "%s", np ? parts[np - 1] : "");
|
||||
if (np) { size_t l = strlen(cur); snprintf(cur + l, sizeof cur - l, "/%s", parts[np - 1]); }
|
||||
snprintf(out, n, "%s", cur);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static long used_kb(const char *dir)
|
||||
{
|
||||
long kb = 0;
|
||||
DIR *d = opendir(dir);
|
||||
struct dirent *e;
|
||||
while (d && (e = readdir(d))) {
|
||||
if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue;
|
||||
char p[1100]; struct stat st;
|
||||
snprintf(p, sizeof p, "%s/%s", dir, e->d_name);
|
||||
if (stat(p, &st)) continue;
|
||||
kb += S_ISDIR(st.st_mode) ? 1 + used_kb(p) : (file_size(p) + 1023) / 1024;
|
||||
}
|
||||
if (d) closedir(d);
|
||||
return kb;
|
||||
}
|
||||
|
||||
/* 0x0012DA60 / 0x0012EA38: sceMcInit and friends. Nothing to set up. */
|
||||
s32 uyafn_12DA60(void) { return 0; }
|
||||
|
||||
/* 0x0012E2E0: sceMcSync(mode, cmd*, result*): 1 = a command finished (its id
|
||||
* and result are stored), -1 = no command pending. Commands finish at once, so
|
||||
* it never reports "still running". */
|
||||
s32 uyafn_12E2E0(s32 mode, s32 *cmd, s32 *result)
|
||||
{
|
||||
(void)mode;
|
||||
if (!pend_cmd) return -1;
|
||||
if (cmd) *cmd = pend_cmd;
|
||||
if (result) *result = pend_res;
|
||||
pend_cmd = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 0x0012E400: sceMcGetInfo(port, slot, type*, free*, format*) */
|
||||
s32 uyafn_12E400(s32 port, s32 slot, s32 *type, s32 *free_kb, s32 *format)
|
||||
{
|
||||
(void)slot;
|
||||
static int told[2];
|
||||
if (!told[port & 1]) { told[port & 1] = 1; uya_log("memory card: slot %d is %s (%s)", (port & 1) + 1, slot_dir(port), card_present(port) ? "inserted" : "empty"); }
|
||||
if (!card_present(port)) {
|
||||
seen[port & 1] = 0;
|
||||
if (type) *type = 0;
|
||||
finish(1, -10);
|
||||
return 0;
|
||||
}
|
||||
if (type) *type = 2; /* PS2 memory card */
|
||||
if (format) *format = 1;
|
||||
if (free_kb) { long f = CARD_KB - 1 - used_kb(slot_dir(port)); *free_kb = f < 0 ? 0 : (s32)f; }
|
||||
finish(1, seen[port & 1] ? 0 : -1); /* first look: "a formatted card was inserted" */
|
||||
seen[port & 1] = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* sceMcGetDir table entry (sceMcTblGetDir), 0x40 bytes */
|
||||
typedef struct {
|
||||
u8 create[8], modify[8];
|
||||
u32 size;
|
||||
u16 attr, r1;
|
||||
u32 r2, pda;
|
||||
char name[32];
|
||||
} McDirEnt;
|
||||
|
||||
static void fill_ent(McDirEnt *e, const char *name, int dir, u32 size)
|
||||
{
|
||||
memset(e, 0, sizeof *e);
|
||||
e->create[2] = e->modify[2] = 0; /* sec, min, hour left zero */
|
||||
e->create[4] = e->modify[4] = 1; /* day 1 */
|
||||
e->create[5] = e->modify[5] = 1; /* month 1 */
|
||||
e->create[6] = e->modify[6] = 2004 & 0xFF; e->create[7] = e->modify[7] = 2004 >> 8;
|
||||
e->size = size;
|
||||
e->attr = dir ? 0x8427 : 0x8497;
|
||||
snprintf(e->name, sizeof e->name, "%s", name);
|
||||
}
|
||||
|
||||
static struct { char dir[1024], pat[64]; int pos; } gd;
|
||||
|
||||
static s32 list_dir(McDirEnt *out, s32 maxent)
|
||||
{
|
||||
DIR *d = opendir(gd.dir);
|
||||
if (!d) return -4;
|
||||
struct dirent *e;
|
||||
int idx = 0, n = 0, top = !strcmp(gd.dir, slot_dir(0)) || !strcmp(gd.dir, slot_dir(1));
|
||||
/* a subdirectory lists "." and ".." first, like the card does */
|
||||
if (!top) {
|
||||
const char *dots[2] = {".", ".."};
|
||||
for (int k = 0; k < 2; k++)
|
||||
if (glob_match(gd.pat, dots[k])) {
|
||||
if (idx++ >= gd.pos && n < maxent) fill_ent(&out[n++], dots[k], 1, 0);
|
||||
}
|
||||
}
|
||||
while ((e = readdir(d)) && n < maxent) {
|
||||
if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue;
|
||||
if (!glob_match(gd.pat, e->d_name)) continue;
|
||||
char p[1200]; struct stat st;
|
||||
snprintf(p, sizeof p, "%s/%s", gd.dir, e->d_name);
|
||||
if (stat(p, &st)) continue;
|
||||
if (idx++ < gd.pos) continue;
|
||||
fill_ent(&out[n++], e->d_name, S_ISDIR(st.st_mode), S_ISDIR(st.st_mode) ? 0 : (u32)file_size(p));
|
||||
}
|
||||
closedir(d);
|
||||
gd.pos += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
/* 0x0012E580: sceMcGetDir(port, slot, name, mode, maxent, table): mode 0
|
||||
* starts a listing of name (wildcards allowed in its last part), mode 1
|
||||
* continues it. The result is the number of entries written. */
|
||||
s32 uyafn_12E580(s32 port, s32 slot, char *name, s32 mode, s32 maxent, void *table)
|
||||
{
|
||||
(void)slot;
|
||||
if (!card_present(port)) { finish(0xD, -10); return 0; }
|
||||
if (mclog()) uya_log("memory card: list %s mode %d max %d", name, mode, maxent);
|
||||
if (mode == 0) {
|
||||
char last[64];
|
||||
if (!host_path(port, name, gd.dir, sizeof gd.dir, last, sizeof last)) { finish(0xD, 0); gd.dir[0] = 0; return 0; }
|
||||
char *sl = strrchr(gd.dir, '/');
|
||||
if (sl) *sl = 0; /* gd.dir = the folder, gd.pat = the pattern in it */
|
||||
snprintf(gd.pat, sizeof gd.pat, "%s", last[0] ? last : "*");
|
||||
gd.pos = 0;
|
||||
}
|
||||
if (!gd.dir[0]) { finish(0xD, 0); return 0; }
|
||||
s32 r = list_dir((McDirEnt *)table, maxent);
|
||||
finish(0xD, r == -4 ? 0 : r);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static FILE *fds[8];
|
||||
|
||||
/* 0x0012DCA0: sceMcOpen(port, slot, name, mode): mode 1 read, 2 write, 3 both,
|
||||
* 0x200 create, 0x40 make a directory (what sceMcMkdir sends). Result: the
|
||||
* file descriptor, or an error. */
|
||||
s32 uyafn_12DCA0(s32 port, s32 slot, char *name, s32 mode)
|
||||
{
|
||||
(void)slot;
|
||||
char p[1024];
|
||||
s32 cmd = (mode & 0x40) ? 0xB : 2;
|
||||
if (!card_present(port)) { finish(cmd, -10); return 0; }
|
||||
if (mclog()) uya_log("memory card: open %s mode 0x%X", name, mode);
|
||||
if (!host_path(port, name, p, sizeof p, 0, 0)) { finish(cmd, -4); return 0; }
|
||||
if (mode & 0x40) {
|
||||
if (is_dir(p)) { finish(cmd, -4); return 0; }
|
||||
finish(cmd, mkdir_one(p) == 0 ? 0 : -4);
|
||||
return 0;
|
||||
}
|
||||
int fd;
|
||||
for (fd = 0; fd < 8 && fds[fd]; fd++);
|
||||
if (fd == 8) { finish(cmd, -7); return 0; }
|
||||
struct stat st;
|
||||
int exists = stat(p, &st) == 0;
|
||||
if (exists && S_ISDIR(st.st_mode)) { finish(cmd, -5); return 0; }
|
||||
if (!exists && !(mode & 0x200)) { finish(cmd, -4); return 0; }
|
||||
FILE *f = fopen(p, exists ? ((mode & 2) ? "r+b" : "rb") : "w+b");
|
||||
if (!f) { finish(cmd, -4); return 0; }
|
||||
fds[fd] = f;
|
||||
finish(cmd, fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 0x0012DDC8: sceMcMkdir(port, slot, name) */
|
||||
s32 uyafn_12DDC8(s32 port, s32 slot, char *name) { return uyafn_12DCA0(port, slot, name, 0x40); }
|
||||
|
||||
/* 0x0012DE00: sceMcClose(fd) */
|
||||
s32 uyafn_12DE00(s32 fd)
|
||||
{
|
||||
if (fd < 0 || fd >= 8 || !fds[fd]) { finish(3, -4); return 0; }
|
||||
fclose(fds[fd]); fds[fd] = 0;
|
||||
finish(3, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 0x0012DEC0: sceMcSeek(fd, offset, whence 0/1/2): result is the new position */
|
||||
s32 uyafn_12DEC0(s32 fd, s32 off, s32 whence)
|
||||
{
|
||||
if (fd < 0 || fd >= 8 || !fds[fd]) { finish(4, -4); return 0; }
|
||||
fseek(fds[fd], off, whence == 1 ? SEEK_CUR : whence == 2 ? SEEK_END : SEEK_SET);
|
||||
finish(4, (s32)ftell(fds[fd]));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 0x0012E050: sceMcRead(fd, buf, size): result is the byte count */
|
||||
s32 uyafn_12E050(s32 fd, void *buf, s32 size)
|
||||
{
|
||||
if (fd < 0 || fd >= 8 || !fds[fd]) { finish(5, -4); return 0; }
|
||||
finish(5, (s32)fread(buf, 1, (size_t)size, fds[fd]));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 0x0012E168: sceMcWrite(fd, buf, size): result is the byte count */
|
||||
s32 uyafn_12E168(s32 fd, void *buf, s32 size)
|
||||
{
|
||||
if (fd < 0 || fd >= 8 || !fds[fd]) { finish(6, -4); return 0; }
|
||||
s32 n = (s32)fwrite(buf, 1, (size_t)size, fds[fd]);
|
||||
fflush(fds[fd]);
|
||||
finish(6, n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 0x0012E7A8: sceMcDelete(port, slot, name): a file, or an empty folder */
|
||||
s32 uyafn_12E7A8(s32 port, s32 slot, char *name)
|
||||
{
|
||||
(void)slot;
|
||||
char p[1024];
|
||||
if (!card_present(port)) { finish(0xF, -10); return 0; }
|
||||
if (!host_path(port, name, p, sizeof p, 0, 0)) { finish(0xF, -4); return 0; }
|
||||
if (is_dir(p)) {
|
||||
DIR *d = opendir(p); struct dirent *e; int empty = 1;
|
||||
while (d && (e = readdir(d))) if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..")) empty = 0;
|
||||
if (d) closedir(d);
|
||||
if (!empty) { finish(0xF, -6); return 0; }
|
||||
finish(0xF, rmdir_one(p) == 0 ? 0 : -5);
|
||||
return 0;
|
||||
}
|
||||
finish(0xF, remove(p) == 0 ? 0 : -4);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 0x0012E6D8: sceMcFormat(port, slot). The folder card is always formatted. */
|
||||
s32 uyafn_12E6D8(s32 port, s32 slot) { (void)slot; finish(0x10, card_present(port) ? 0 : -10); return 0; }
|
||||
|
||||
/* 0x0012E8D8: sceMcUnformat(port, slot). Not done to a folder: reports success. */
|
||||
s32 uyafn_12E8D8(s32 port, s32 slot) { (void)slot; finish(0x11, card_present(port) ? 0 : -10); return 0; }
|
||||
|
||||
/* Trace of the menu's card manager (uyafn_397490, state at 0x00142430) and
|
||||
* of the card dialogs (UI state 0x001D4CE8, flags 0x001D4CEC). Called every
|
||||
* vsync; logs a line whenever one of the watched values changes. The manager's
|
||||
* idle polling (states 0 to 2) is left out. */
|
||||
void uya_mc_trace(void)
|
||||
{
|
||||
if (!mclog()) return;
|
||||
const u8 *m = (const u8 *)0x00142430;
|
||||
s32 v[9];
|
||||
s32 st = *(const s32 *)(m + 0x15C);
|
||||
v[0] = st > 2 ? st : 0; /* job running (3 and up) */
|
||||
v[1] = *(const s32 *)(m + 0x164); /* job requested, -1 none */
|
||||
v[2] = *(const s32 *)(m + 0x16C); /* last error/event code */
|
||||
v[3] = *(const s32 *)(m + 0x10); /* slot 1 "card changed" flag */
|
||||
v[4] = *(const s16 *)(m + 0x18); /* slot 1 save state */
|
||||
v[5] = *(const s32 *)(m + 0x160); /* job step */
|
||||
v[6] = *(const s32 *)0x001D4CE8; /* card dialog state */
|
||||
v[7] = *(const s32 *)0x001D4CEC; /* card dialog flags */
|
||||
v[8] = *(const s32 *)(m + 0xA0 + 0x10); /* slot 2 "card changed" flag */
|
||||
static s32 prev[9]; static int init;
|
||||
if (init && !memcmp(prev, v, sizeof v)) return;
|
||||
init = 1; memcpy(prev, v, sizeof v);
|
||||
uya_log("card mgr: job %d step %d req %d err 0x%X | slot1 changed %d save %d | slot2 changed %d | dialog %d flags 0x%X",
|
||||
v[0], v[5], v[1], v[2], v[3], v[4], v[8], v[6], v[7]);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/* libpad / libmtap: one plain digital controller on port 0, driven by the
|
||||
* keyboard (see plat_pad_buttons). Reporting a digital pad skips the DualShock
|
||||
* actuator setup in the menu's pad state machine (uyafn_39E4B0). */
|
||||
#include "uya_host.h"
|
||||
unsigned plat_pad_buttons(void); /* keyboard (Windows) or UYA_KEYS script (Linux), 1 = held */
|
||||
|
||||
/* 0x0012F470: scePadGetState(port, slot); 6 = stable (ready). */
|
||||
s32 uyafn_12F470(s32 port, s32 slot) { return (port == 0 && slot == 0) ? 6 : 0; }
|
||||
|
||||
/* 0x0012F728: scePadInfoMode(port, slot, term, offs); term 1 = current ID (4 = digital), 2 = extended ID (0 = none). */
|
||||
s32 uyafn_12F728(s32 port, s32 slot, s32 term, s32 offs)
|
||||
{
|
||||
(void)port; (void)slot; (void)offs;
|
||||
return term == 1 ? 4 : 0;
|
||||
}
|
||||
|
||||
/* 0x0012EBB8: sceMtapGetConnection(port); 0 = no multitap. */
|
||||
s32 uyafn_12EBB8(s32 port) { (void)port; return 0; }
|
||||
|
||||
/* 0x0012F3F8: scePadRead(port, slot, buf): 32 bytes; [0] 0 = ok, [1] 0x41 = digital, [2..3] buttons (1 = released). */
|
||||
s32 uyafn_12F3F8(s32 port, s32 slot, u8 *buf)
|
||||
{
|
||||
for (int i = 0; i < 32; i++) buf[i] = 0;
|
||||
if (port || slot) return 0;
|
||||
buf[1] = 0x41;
|
||||
{
|
||||
unsigned held = plat_pad_buttons();
|
||||
buf[2] = (u8)~(held & 0xFF); /* Select L3 R3 Start Up Right Down Left */
|
||||
buf[3] = (u8)~((held >> 8) & 0xFF); /* L2 R2 L1 R1 Triangle Circle Cross Square */
|
||||
}
|
||||
for (int i = 4; i < 8; i++) buf[i] = 0x80; /* sticks centred, if anyone looks */
|
||||
return 32;
|
||||
}
|
||||
|
||||
/* 0x0012F5A8: scePadGetReqState(port, slot); 0 = complete. */
|
||||
s32 uyafn_12F5A8(s32 port, s32 slot) { (void)port; (void)slot; return 0; }
|
||||
|
||||
/* 0x0012F918: scePadSetActAlign(port, slot, data): maps the pad's vibration
|
||||
* motors. There is no vibration on the keyboard, so it reports success. */
|
||||
s32 uyafn_12F918(s32 port, s32 slot, u8 *data) { (void)port; (void)slot; (void)data; return 1; }
|
||||
@@ -0,0 +1,353 @@
|
||||
/* 989snd (Sony's sound library, EE side) for the menu's sound effects, with a
|
||||
* small software mixer in place of the SPU2.
|
||||
*
|
||||
* Banks are 989snd "SBlk" version 3 banks (sounds -> grains -> tones -> PS2
|
||||
* ADPCM waveforms). Loading copies the bank into host memory. Playing a sound
|
||||
* starts one voice per "play tone" grain of the sound; voices are decoded and
|
||||
* mixed at 48 kHz stereo and streamed to the platform's audio output each
|
||||
* vsync (about 100 ms ahead). Volume (0-0x400), pan (degrees) and pitch (1/128 semitone) follow the
|
||||
* library's units. Completion callbacks are queued and delivered on the next
|
||||
* flush (uyafn_13B620), like the asynchronous library.
|
||||
*
|
||||
* The SPU2's ADSR envelopes are applied; a stop is a key-off (release).
|
||||
* Not done: reverb, the grain types other than "play tone" (random choice,
|
||||
* delays...), music streams (uyafn_13CB20) and master volume groups.
|
||||
* The menu also calls these every frame and while it waits for disc reads, so
|
||||
* they also deliver finished disc reads. */
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "uya_host.h"
|
||||
#include "uya_runtime.h"
|
||||
|
||||
void uya_cd_service(void);
|
||||
|
||||
/* ---- banks ---- */
|
||||
#define MAX_BANKS 16
|
||||
typedef struct { u8 *data; u32 size; const u8 *sblk; const u8 *wave; u32 wave_size; } bank_t;
|
||||
static bank_t banks[MAX_BANKS]; /* handle = index + 1 */
|
||||
|
||||
static u32 rd32(const u8 *p) { return p[0] | (p[1] << 8) | (p[2] << 16) | ((u32)p[3] << 24); }
|
||||
static u16 rd16(const u8 *p) { return (u16)(p[0] | (p[1] << 8)); }
|
||||
|
||||
static s32 bank_add(const u8 *src, u32 size)
|
||||
{
|
||||
if (size < 0x18 || rd32(src + 8) + 4 > size || memcmp(src + rd32(src + 8), "SBlk", 4)) {
|
||||
uya_log("sound: not a 989snd bank");
|
||||
return 0;
|
||||
}
|
||||
for (int i = 0; i < MAX_BANKS; i++) {
|
||||
if (banks[i].data) continue;
|
||||
u8 *d = malloc(size);
|
||||
if (!d) return 0;
|
||||
memcpy(d, src, size);
|
||||
banks[i].data = d;
|
||||
banks[i].size = size;
|
||||
banks[i].sblk = d + rd32(d + 8);
|
||||
banks[i].wave = d + rd32(d + 0x10);
|
||||
banks[i].wave_size = rd32(d + 0x14);
|
||||
uya_log("sound: bank %d loaded (%u sounds, %u KB of samples)", i + 1, rd16(banks[i].sblk + 0x16), banks[i].wave_size / 1024);
|
||||
return i + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 bank_size(const u8 *b) { return rd32(b + 0x10) + rd32(b + 0x14); }
|
||||
|
||||
/* ---- callbacks, delivered on the next flush ---- */
|
||||
typedef void (*snd_cb)(s32 handle, u64 user);
|
||||
static struct { snd_cb cb; s32 h; u64 user; } cbq[256];
|
||||
static int ncb;
|
||||
|
||||
static void queue_cb(void *cb, s32 h, u64 user)
|
||||
{
|
||||
if (!cb || ncb >= 256) return;
|
||||
cbq[ncb].cb = (snd_cb)cb; cbq[ncb].h = h; cbq[ncb].user = user; ncb++;
|
||||
}
|
||||
|
||||
static void run_cbs(void)
|
||||
{
|
||||
int n = ncb;
|
||||
ncb = 0;
|
||||
for (int i = 0; i < n; i++) cbq[i].cb(cbq[i].h, cbq[i].user);
|
||||
}
|
||||
|
||||
/* ---- voices ---- */
|
||||
#define MAX_VOICES 48
|
||||
typedef struct {
|
||||
int on;
|
||||
s32 handle;
|
||||
const u8 *data, *end, *loop;
|
||||
s32 hist1, hist2;
|
||||
s16 buf[28];
|
||||
int bufpos; /* next sample in buf, 28 = decode next frame */
|
||||
double frac, step; /* resampling */
|
||||
s16 hist4[4]; /* last four samples for the interpolation */
|
||||
int env_phase; /* 0 attack, 1 decay, 2 sustain, 3 release */
|
||||
int env_level, env_count;
|
||||
u16 adsr1, adsr2;
|
||||
float gl, gr;
|
||||
double base_rate;
|
||||
int tone_pan;
|
||||
float tone_vol;
|
||||
} voice_t;
|
||||
static voice_t voices[MAX_VOICES];
|
||||
static s32 next_handle = 1;
|
||||
|
||||
static int decode_frame(voice_t *v)
|
||||
{
|
||||
static const int fk[5][2] = {{0, 0}, {60, 0}, {115, -52}, {98, -55}, {122, -60}};
|
||||
if (v->data + 16 > v->end) return 0;
|
||||
const u8 *fr = v->data;
|
||||
int sh = fr[0] & 15, flt = (fr[0] >> 4) & 7, flags = fr[1];
|
||||
if (flt > 4) flt = 0;
|
||||
if (sh > 12) sh = 9;
|
||||
if (flags & 4) v->loop = fr;
|
||||
for (int s = 0; s < 28; s++) {
|
||||
int nib = (fr[2 + s / 2] >> ((s & 1) * 4)) & 15;
|
||||
if (nib >= 8) nib -= 16;
|
||||
int x = (nib << 12) >> sh;
|
||||
x += (v->hist1 * fk[flt][0] + v->hist2 * fk[flt][1] + 32) >> 6;
|
||||
if (x > 32767) x = 32767;
|
||||
if (x < -32768) x = -32768;
|
||||
v->hist2 = v->hist1;
|
||||
v->hist1 = x;
|
||||
v->buf[s] = (s16)x;
|
||||
}
|
||||
v->bufpos = 0;
|
||||
v->data += 16;
|
||||
if (flags & 1) { /* end of the waveform */
|
||||
if ((flags & 2) && v->loop) v->data = v->loop;
|
||||
else v->end = v->data; /* play this frame, then stop */
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int next_sample(voice_t *v, s16 *out)
|
||||
{
|
||||
if (v->bufpos >= 28 && !decode_frame(v)) return 0;
|
||||
*out = v->buf[v->bufpos++];
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void set_mix(voice_t *v, s32 vol, s32 pan, s32 pitch)
|
||||
{
|
||||
int deg = ((pan + v->tone_pan) % 360 + 360) % 360;
|
||||
double x = sin((double)deg * 3.14159265358979 / 180.0); /* -1 left .. 1 right */
|
||||
float g = (float)(vol > 0x400 ? 0x400 : (vol < 0 ? 0 : vol)) / 1024.0f * v->tone_vol;
|
||||
v->gl = g * (float)sqrt((1.0 - x) * 0.5);
|
||||
v->gr = g * (float)sqrt((1.0 + x) * 0.5);
|
||||
v->step = v->base_rate * pow(2.0, (double)pitch / 1536.0) / 48000.0;
|
||||
}
|
||||
|
||||
/* rate of a tone: centre note (0x80 and up: 48 kHz base) and fine tune, for note 60 */
|
||||
static double tone_rate(u8 center, u8 fine)
|
||||
{
|
||||
double base = 44100.0;
|
||||
int c = center;
|
||||
if (center & 0x80) { c = 0x100 - center; base = 48000.0; }
|
||||
return base * pow(2.0, ((60 - c) + fine / 128.0) / 12.0);
|
||||
}
|
||||
|
||||
static s32 play_sound(s32 bank, s32 sound, s32 vol, s32 pan, s32 pitch)
|
||||
{
|
||||
if (bank < 1 || bank > MAX_BANKS || !banks[bank - 1].data) return 0;
|
||||
bank_t *b = &banks[bank - 1];
|
||||
const u8 *s = b->sblk;
|
||||
u32 nsounds = rd16(s + 0x16);
|
||||
if (sound < 0 || (u32)sound >= nsounds) return 0;
|
||||
const u8 *t1 = s + rd32(s + 0x1C), *t2 = s + rd32(s + 0x20), *t3 = s + rd32(s + 0x34);
|
||||
const u8 *se = t1 + sound * 12;
|
||||
int ngrains = se[4];
|
||||
u32 goff = rd32(se + 8);
|
||||
float svol = se[0] / 127.0f;
|
||||
s32 h = 0;
|
||||
for (int g = 0; g < ngrains; g++) {
|
||||
const u8 *gr = t2 + goff + g * 8;
|
||||
u32 val = rd32(gr);
|
||||
if ((val >> 16) != 0x0100) continue; /* only "play tone" grains */
|
||||
const u8 *tone = t3 + (val & 0xFFFF);
|
||||
u32 off = rd32(tone + 0x10), size = rd32(tone + 0x14);
|
||||
if (off >= b->wave_size) continue;
|
||||
if (off + size > b->wave_size || size == 0) size = b->wave_size - off;
|
||||
int vi;
|
||||
for (vi = 0; vi < MAX_VOICES && voices[vi].on; vi++) {}
|
||||
if (vi == MAX_VOICES) break;
|
||||
if (!h) { h = next_handle++; if (next_handle > 0x7FFFFFFF) next_handle = 1; }
|
||||
voice_t *v = &voices[vi];
|
||||
memset(v, 0, sizeof *v);
|
||||
v->on = 1;
|
||||
v->handle = h;
|
||||
v->data = b->wave + off;
|
||||
v->end = b->wave + off + size;
|
||||
v->bufpos = 28;
|
||||
v->frac = 4.0; /* load the first samples */
|
||||
v->adsr1 = rd16(tone + 0xA);
|
||||
v->adsr2 = rd16(tone + 0xC);
|
||||
v->base_rate = tone_rate(tone[2], tone[3]);
|
||||
v->tone_vol = svol * ((tone[1] > 127 ? 127 : tone[1]) / 127.0f);
|
||||
v->tone_pan = (s16)rd16(tone + 4);
|
||||
set_mix(v, vol, pan, pitch);
|
||||
if (getenv("UYA_SNDLOG")) uya_log("sound: play bank %d sound %d tone at 0x%X, %.0f Hz, vol %d pan %d pitch %d", bank, sound, off, v->base_rate, vol, pan, pitch);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
static int handle_playing(s32 h)
|
||||
{
|
||||
if (!h || h == -1) return 0;
|
||||
for (int i = 0; i < MAX_VOICES; i++) if (voices[i].on && voices[i].handle == h) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The SPU2's ADSR envelope, one step per 48 kHz sample (rates as on the SPU). */
|
||||
static void env_step(voice_t *v)
|
||||
{
|
||||
int rate, inc, expo;
|
||||
switch (v->env_phase) {
|
||||
case 0: rate = (v->adsr1 >> 8) & 0x7F; inc = 1; expo = v->adsr1 >> 15; break;
|
||||
case 1: rate = ((v->adsr1 >> 4) & 0xF) << 2; inc = 0; expo = 1; break;
|
||||
case 2: rate = (v->adsr2 >> 6) & 0x7F; inc = !((v->adsr2 >> 14) & 1); expo = v->adsr2 >> 15; break;
|
||||
default: rate = (v->adsr2 & 0x1F) << 2; inc = 0; expo = (v->adsr2 >> 5) & 1; break;
|
||||
}
|
||||
if (v->env_phase == 2 && rate == 0x7F) return; /* sustain held */
|
||||
int shift = rate >> 2;
|
||||
int step = inc ? 7 - (rate & 3) : -8 + (rate & 3);
|
||||
int cycles = 1 << (shift > 11 ? shift - 11 : 0);
|
||||
step <<= (shift < 11 ? 11 - shift : 0);
|
||||
if (expo && inc && v->env_level > 0x6000) cycles *= 4;
|
||||
if (expo && !inc) step = step * v->env_level >> 15;
|
||||
if (++v->env_count < cycles) return;
|
||||
v->env_count = 0;
|
||||
v->env_level += step;
|
||||
if (v->env_level > 0x7FFF) v->env_level = 0x7FFF;
|
||||
if (v->env_level < 0) v->env_level = 0;
|
||||
if (v->env_phase == 0 && v->env_level >= 0x7FFF) v->env_phase = 1;
|
||||
else if (v->env_phase == 1 && v->env_level <= (((v->adsr1 & 0xF) + 1) * 0x800)) v->env_phase = 2;
|
||||
else if (v->env_phase == 3 && v->env_level == 0) v->on = 0;
|
||||
}
|
||||
|
||||
/* Mixes `frames` stereo frames (48 kHz). Resampling is 4-point cubic
|
||||
* B-spline, a smoothing filter like the SPU's Gaussian interpolation. */
|
||||
static void mix(s16 *out, int frames)
|
||||
{
|
||||
for (int f = 0; f < frames; f++) {
|
||||
float l = 0, r = 0;
|
||||
for (int i = 0; i < MAX_VOICES; i++) {
|
||||
voice_t *v = &voices[i];
|
||||
if (!v->on) continue;
|
||||
while (v->frac >= 1.0) {
|
||||
s16 x;
|
||||
if (!next_sample(v, &x)) { v->on = 0; break; }
|
||||
v->hist4[0] = v->hist4[1]; v->hist4[1] = v->hist4[2]; v->hist4[2] = v->hist4[3]; v->hist4[3] = x;
|
||||
v->frac -= 1.0;
|
||||
}
|
||||
if (!v->on) continue;
|
||||
float t = (float)v->frac, t2 = t * t, t3 = t2 * t;
|
||||
float w0 = (1 - t) * (1 - t) * (1 - t) / 6.0f;
|
||||
float w1 = (3 * t3 - 6 * t2 + 4) / 6.0f;
|
||||
float w2 = (-3 * t3 + 3 * t2 + 3 * t + 1) / 6.0f;
|
||||
float w3 = t3 / 6.0f;
|
||||
float s = v->hist4[0] * w0 + v->hist4[1] * w1 + v->hist4[2] * w2 + v->hist4[3] * w3;
|
||||
env_step(v);
|
||||
s *= v->env_level / 32768.0f;
|
||||
l += s * v->gl;
|
||||
r += s * v->gr;
|
||||
v->frac += v->step;
|
||||
}
|
||||
out[f * 2] = (s16)(l > 32767 ? 32767 : (l < -32768 ? -32768 : l));
|
||||
out[f * 2 + 1] = (s16)(r > 32767 ? 32767 : (r < -32768 ? -32768 : r));
|
||||
}
|
||||
}
|
||||
|
||||
/* RAC1: uya_sound_pump (called once per vsync) is in host/src/rac1/snd989.c,
|
||||
* which mixes the host IOP driver's voices; the UYA mixer above is unused. */
|
||||
|
||||
/* ---- the library calls ---- */
|
||||
|
||||
/* 0x0013B620: flush queued commands; 0 = nothing pending. Delivers callbacks. */
|
||||
s32 uyafn_13B620(void) { uya_cd_service(); run_cbs(); return 0; }
|
||||
/* 0x0013CA28, 0x0013CA20: per-frame library service. */
|
||||
void uyafn_13CA28(void) { uya_cd_service(); }
|
||||
void uyafn_13CA20(void) { uya_cd_service(); }
|
||||
|
||||
/* 0x0013BC30: load a bank from EE memory; cb(handle, user) when done. */
|
||||
s32 uyafn_13BC30(u32 addr, void *cb, u64 user)
|
||||
{
|
||||
const u8 *src = (const u8 *)(uintptr_t)addr;
|
||||
s32 h = bank_add(src, bank_size(src));
|
||||
queue_cb(cb, h, user);
|
||||
return h;
|
||||
}
|
||||
|
||||
/* 0x0013BAB8: load a bank from the disc at sector lsn; cb(handle, user) when done. */
|
||||
s32 uyafn_13BAB8(s32 lsn, s32 offset, void *cb, u64 user)
|
||||
{
|
||||
u8 head[2048];
|
||||
s32 h = 0;
|
||||
(void)offset;
|
||||
if (uya_disc_read((u32)lsn, 1, head) == 0) {
|
||||
u32 size = bank_size(head);
|
||||
u32 sectors = (size + 2047) / 2048;
|
||||
u8 *b = (size > 0x18 && size < (16u << 20)) ? malloc((size_t)sectors * 2048) : 0;
|
||||
if (b && uya_disc_read((u32)lsn, sectors, b) == 0) h = bank_add(b, size);
|
||||
free(b);
|
||||
}
|
||||
queue_cb(cb, h, user);
|
||||
return h;
|
||||
}
|
||||
|
||||
/* 0x0013C0B8: play `sound` from `bank` with volume, pan, pitch mod and pitch bend. */
|
||||
s32 uyafn_13C0B8(s32 bank, s32 sound, s32 vol, s32 pan, s32 pitch, s32 pb, void *cb, u32 user)
|
||||
{
|
||||
(void)pb;
|
||||
s32 h = play_sound(bank, sound, vol, pan, pitch);
|
||||
queue_cb(cb, h, user);
|
||||
return h;
|
||||
}
|
||||
|
||||
/* 0x0013C2C0: update a playing sound (mode bits: 1 volume, 2 pan, 8 pitch, 0x10 bend). */
|
||||
s32 uyafn_13C2C0(s32 h, s32 mode, s32 vol, s32 pan, s32 pitch, s32 pb, void *cb, u32 user)
|
||||
{
|
||||
(void)mode; (void)pb;
|
||||
for (int i = 0; i < MAX_VOICES; i++)
|
||||
if (voices[i].on && voices[i].handle == h) set_mix(&voices[i], vol, pan, pitch);
|
||||
queue_cb(cb, handle_playing(h) ? h : 0, user);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 0x0013C200: is the sound still playing? cb(handle or 0, user). */
|
||||
s32 uyafn_13C200(s32 h, void *cb, u32 user)
|
||||
{
|
||||
queue_cb(cb, handle_playing(h) ? h : 0, user);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 0x0013C118: stop a sound. */
|
||||
void uyafn_13C118(s32 h)
|
||||
{
|
||||
for (int i = 0; i < MAX_VOICES; i++)
|
||||
if (voices[i].on && voices[i].handle == h) { voices[i].env_phase = 3; voices[i].env_count = 0; } /* key off: release */
|
||||
}
|
||||
|
||||
/* 0x0013C170: stop every sound. */
|
||||
void uyafn_13C170(void) { for (int i = 0; i < MAX_VOICES; i++) voices[i].on = 0; }
|
||||
/* 0x0013BF00, 0x0013BED0: unload a bank (kept: the handle is not reused). */
|
||||
void uyafn_13BF00(s32 h) { (void)h; }
|
||||
void uyafn_13BED0(s32 h) { (void)h; }
|
||||
|
||||
/* 0x0013BF30: master volume of a group (not applied yet). */
|
||||
void uyafn_13BF30(s32 group, s32 vol) { (void)group; (void)vol; }
|
||||
/* 0x0013CF80, 0x0013CFF8: reverb set-up (not emulated). */
|
||||
void uyafn_13CF80(s32 a, s32 b, s32 c, s32 d, s32 e) { (void)a; (void)b; (void)c; (void)d; (void)e; }
|
||||
void uyafn_13CFF8(s32 a, s32 b, s32 c, s32 d) { (void)a; (void)b; (void)c; (void)d; }
|
||||
/* 0x0013D1E8: distance to pitch shift (Doppler); none. */
|
||||
s32 uyafn_13D1E8(s32 d) { (void)d; return 0; }
|
||||
|
||||
/* 0x0013CAE8 and 0x0013CD00(n): 989snd commands 0x34 and 0x36, which the menu
|
||||
* sends after stopping every sound when a movie starts (uyafn_39CBA0). They
|
||||
* act on the music stream player, which the host does not have yet, so both
|
||||
* succeed and do nothing. */
|
||||
s32 uyafn_13CAE8(void) { return 0; }
|
||||
s32 uyafn_13CD00(s32 n) { (void)n; return 0; }
|
||||
@@ -0,0 +1,104 @@
|
||||
/* rac1_wrapper.exe: reserves PS2 RAM through its own image, then runs rac1_game.dll.
|
||||
*
|
||||
* Built with no C runtime and image base 0x10000. Its .ps2ram section (see
|
||||
* ps2ram_win32.s) is uninitialized and runs past 0x02000000, so Windows maps it
|
||||
* with the image, before it places the main stack and the process heaps.
|
||||
* The few Win32 functions it needs are declared here, so it needs no SDK headers.
|
||||
*
|
||||
* Delivery in parts: with every level built in, rac1_game.dll is larger than the
|
||||
* file transfer allows, so it can arrive as rac1_game.dll.part00, .part01, ... plus
|
||||
* rac1_game.dll.manifest ("<parts> <total bytes>"). On start, if the manifest is
|
||||
* there, the parts are joined into rac1_game.dll (through a temporary file, the size
|
||||
* checked against the manifest) and the parts and manifest are deleted.
|
||||
* No globals here: they would land in .bss next to the PS2 RAM. */
|
||||
typedef void *HMODULE;
|
||||
typedef void *HANDLE;
|
||||
__declspec(dllimport) HMODULE __stdcall LoadLibraryA(const char *);
|
||||
__declspec(dllimport) void *__stdcall GetProcAddress(HMODULE, const char *);
|
||||
__declspec(dllimport) void __stdcall ExitProcess(unsigned);
|
||||
__declspec(dllimport) int __stdcall MessageBoxA(void *, const char *, const char *, unsigned);
|
||||
__declspec(dllimport) HANDLE __stdcall CreateFileA(const char *, unsigned, unsigned, void *, unsigned, unsigned, HANDLE);
|
||||
__declspec(dllimport) int __stdcall ReadFile(HANDLE, void *, unsigned, unsigned *, void *);
|
||||
__declspec(dllimport) int __stdcall WriteFile(HANDLE, const void *, unsigned, unsigned *, void *);
|
||||
__declspec(dllimport) int __stdcall CloseHandle(HANDLE);
|
||||
__declspec(dllimport) int __stdcall DeleteFileA(const char *);
|
||||
__declspec(dllimport) int __stdcall MoveFileExA(const char *, const char *, unsigned);
|
||||
__declspec(dllimport) void *__stdcall VirtualAlloc(void *, unsigned long, unsigned, unsigned);
|
||||
__declspec(dllimport) int __stdcall VirtualFree(void *, unsigned long, unsigned);
|
||||
__declspec(dllimport) unsigned __stdcall GetModuleFileNameA(HMODULE, char *, unsigned);
|
||||
|
||||
#define INVALID ((HANDLE)(long)-1)
|
||||
#define GENERIC_READ 0x80000000u
|
||||
#define GENERIC_WRITE 0x40000000u
|
||||
#define OPEN_EXISTING 3u
|
||||
#define CREATE_ALWAYS 2u
|
||||
|
||||
typedef int (*uya_main_fn)(void);
|
||||
|
||||
/* dir (the exe's folder, with its trailing backslash) + name, plus a two-digit number when i >= 0 */
|
||||
static void path(char *out, const char *dir, const char *name, int i)
|
||||
{
|
||||
int k = 0;
|
||||
while (*dir) out[k++] = *dir++;
|
||||
while (*name) out[k++] = *name++;
|
||||
if (i >= 0) { out[k++] = (char)('0' + i / 10); out[k++] = (char)('0' + i % 10); }
|
||||
out[k] = 0;
|
||||
}
|
||||
|
||||
static void fail(const char *m) { MessageBoxA(0, m, "RAC1", 0x10); ExitProcess(1); }
|
||||
|
||||
/* rac1_game.dll from its parts, when a manifest says they are there */
|
||||
static void join_parts(void)
|
||||
{
|
||||
char dir[600], man[700], tmp[700], dll[700], nm[700];
|
||||
unsigned dl = GetModuleFileNameA(0, dir, 520);
|
||||
while (dl > 0 && dir[dl - 1] != '\\' && dir[dl - 1] != '/') dl--;
|
||||
dir[dl] = 0;
|
||||
path(man, dir, "rac1_game.dll.manifest", -1); path(tmp, dir, "rac1_game.dll.tmp", -1); path(dll, dir, "rac1_game.dll", -1);
|
||||
HANDLE m = CreateFileA(man, GENERIC_READ, 1, 0, OPEN_EXISTING, 0x80, 0);
|
||||
if (m == INVALID) return;
|
||||
char txt[64]; unsigned got = 0;
|
||||
ReadFile(m, txt, sizeof txt - 1, &got, 0);
|
||||
CloseHandle(m);
|
||||
txt[got] = 0;
|
||||
unsigned parts = 0, k = 0; unsigned long long total = 0;
|
||||
while (txt[k] >= '0' && txt[k] <= '9') parts = parts * 10 + (unsigned)(txt[k++] - '0');
|
||||
while (txt[k] == ' ') k++;
|
||||
while (txt[k] >= '0' && txt[k] <= '9') total = total * 10 + (unsigned)(txt[k++] - '0');
|
||||
if (!parts || parts > 99 || !total) fail("rac1_game.dll.manifest is not valid.");
|
||||
unsigned char *buf = VirtualAlloc(0, 1u << 22, 0x3000, 4);
|
||||
if (!buf) fail("Out of memory joining rac1_game.dll.");
|
||||
HANDLE o = CreateFileA(tmp, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0x80, 0);
|
||||
if (o == INVALID) fail("Could not write rac1_game.dll.tmp next to rac1_wrapper.exe.");
|
||||
unsigned long long done = 0;
|
||||
for (unsigned i = 0; i < parts; i++) {
|
||||
path(nm, dir, "rac1_game.dll.part", (int)i);
|
||||
HANDLE p = CreateFileA(nm, GENERIC_READ, 1, 0, OPEN_EXISTING, 0x80, 0);
|
||||
if (p == INVALID) { CloseHandle(o); DeleteFileA(tmp); fail("A part of rac1_game.dll (rac1_game.dll.partNN) is missing."); }
|
||||
for (;;) {
|
||||
unsigned n = 0, w = 0;
|
||||
if (!ReadFile(p, buf, 1u << 22, &n, 0) || !n) break;
|
||||
if (!WriteFile(o, buf, n, &w, 0) || w != n) { CloseHandle(p); CloseHandle(o); fail("Writing rac1_game.dll.tmp failed (disk full?)."); }
|
||||
done += n;
|
||||
}
|
||||
CloseHandle(p);
|
||||
}
|
||||
CloseHandle(o);
|
||||
VirtualFree(buf, 0, 0x8000);
|
||||
if (done != total) { DeleteFileA(tmp); fail("The parts of rac1_game.dll do not add up to its size; copy them again."); }
|
||||
if (!MoveFileExA(tmp, dll, 1 /* REPLACE_EXISTING */)) fail("Could not replace rac1_game.dll (is the game still running?).");
|
||||
for (unsigned i = 0; i < parts; i++) { path(nm, dir, "rac1_game.dll.part", (int)i); DeleteFileA(nm); }
|
||||
DeleteFileA(man);
|
||||
}
|
||||
|
||||
void __stdcall launcher_start(void)
|
||||
{
|
||||
join_parts();
|
||||
HMODULE h = LoadLibraryA("rac1_game.dll");
|
||||
if (!h) {
|
||||
MessageBoxA(0, "rac1_game.dll was not found next to rac1_wrapper.exe.", "UYA Wrapper", 0x10);
|
||||
ExitProcess(1);
|
||||
}
|
||||
uya_main_fn f = (uya_main_fn)GetProcAddress(h, "uya_main_win32");
|
||||
ExitProcess(f ? (unsigned)f() : 1u);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/* PS2 main RAM, part of the launcher image (see launcher_win32.c). A plain
|
||||
* zero-initialized global lands in .bss, so it costs no file space; the
|
||||
* runtime makes it executable. */
|
||||
__attribute__((aligned(4096))) unsigned char uya_ps2ram[0x01FF8000];
|
||||
@@ -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
@@ -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
@@ -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"),
|
||||
]
|
||||
@@ -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
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user