V1.0
@@ -0,0 +1,8 @@
|
||||
# build output and downloads
|
||||
dist/
|
||||
games/*/build/
|
||||
games/*/decomp/
|
||||
games/*/zig-cache/
|
||||
games/*/build_log.txt
|
||||
__pycache__/
|
||||
*.pyc
|
||||
@@ -0,0 +1,77 @@
|
||||
# Project Solana
|
||||
|
||||
Native PC versions of the PlayStation 2 Ratchet & Clank games, built on your own PC from your own disc.
|
||||
|
||||
Project Solana is a launcher and a set of static recompilations. Each game's code is translated ahead of time from the game's executable into C, with the decompiled source from OpenRAC where it exists, and compiled into a native Windows program that reads everything else (models, textures, sound, movies) from your disc image at run time.
|
||||
|
||||
**Nothing from the games is distributed here.** The launcher downloads only tools and source code; the game is built on your machine from a disc image you provide.
|
||||
|
||||
## Playing
|
||||
|
||||
1. Download `Project Solana.exe` from the [latest launcher release](../../releases) and put it in a folder of its own (it keeps its files next to itself).
|
||||
2. Run it and add your disc image: **Add disc image**, **Scan a folder**, or drag an `.iso` onto the window.
|
||||
3. Select the game and press **Build**. The first build downloads about 95 MB (Python and the zig compiler, then the game's build kit) and builds the game from your disc: 10 to 30 minutes depending on your CPU. It only happens once per game version.
|
||||
4. Press **Play**.
|
||||
|
||||
### Supported games
|
||||
|
||||
| Game | Region | Disc executable | Status |
|
||||
|---|---|---|---|
|
||||
| Ratchet & Clank | PAL (v2.00) | `SCES_509.16`, MD5 `d86429e2eca4e1da7d7f1cc656af2574` | Playable start to finish |
|
||||
| Ratchet & Clank | NTSC-U, NTSC-J | `SCUS_971.99`, `SCPS_150.37` | Recognised, not supported yet |
|
||||
| Going Commando, Up Your Arsenal, Deadlocked | all | | Recognised, not supported yet |
|
||||
|
||||
### Requirements
|
||||
|
||||
- Windows 10 or 11, 64-bit
|
||||
- A GPU with OpenGL 3.3 (Upscaled at 3 to 4x wants a GTX 1060 class card or better)
|
||||
- A controller: Xbox, DualShock 4 or DualSense, over USB or Bluetooth
|
||||
- About 2 GB free while building, 300 MB after
|
||||
|
||||
### Settings
|
||||
|
||||
Resolution (Native, or Upscaled to a whole multiple of the PS2's 448 lines that covers your screen), VSync, the graphics threads, performance statistics in the log, the length of the F9 glitch capture, and a level select for testing. **Rebuild the game** fetches the latest build kit and builds again; saves are kept.
|
||||
|
||||
### Reporting problems
|
||||
|
||||
- **Open log** shows `rac1_log.txt` (or the build's `build_log.txt` when a build failed).
|
||||
- **F9** in the game records the next few seconds of graphics input to `gsrec_NN.bin` (about 260 MB per second of capture); attach it to a report together with what looked wrong.
|
||||
|
||||
## Repository layout
|
||||
|
||||
| Path | What |
|
||||
|---|---|
|
||||
| `launcher/` | `Project Solana.exe`: Win32 + GDI, no dependencies. `python launcher/build_launcher.py` builds it (needs zig: `pip install ziglang`) |
|
||||
| `games/rac1/` | Ratchet & Clank's build kit: `build.py`, the recompilers (`tools/`), the host engine (`host/`: GS in OpenGL and software, DMA, VU, SPU2 and 989snd, disc, pad, memory card, movies), the wrapper (`launcher/`) |
|
||||
| `games/rac1/dev/` | developer tools (GS capture replay, VU reference check); not part of the kit |
|
||||
| `release/make_release.py` | makes a game's release files |
|
||||
| `THIRD_PARTY_NOTICES.md` | pl_mpeg (MIT), jak-project's 989snd tables (ISC) |
|
||||
|
||||
## How the Build button works
|
||||
|
||||
The launcher fetches from the release named after the game (`releases/download/rac1/`):
|
||||
|
||||
| File | What |
|
||||
|---|---|
|
||||
| `rac1.json` | version, file names, sizes and SHA-256s, the levels to build |
|
||||
| `toolchain-win64.zip` | Python 3.12 (python-build-standalone), rabbitizer, zig 0.16; shared by all games, kept in `toolchain\` and fetched again only when its version changes |
|
||||
| `rac1-build.zip` | `games/rac1` plus the decomp's source as `decomp/` (no `baserom/`) |
|
||||
|
||||
It checks both zips against their SHA-256s, unpacks them next to itself (`toolchain\`, `games\rac1\`), runs `build.py --platform windows --iso <your disc>`, and then deletes the intermediate files (about 1.3 GB) and the bytes it read from the disc, keeping `games\rac1\build\windows` (the game, its saves and logs).
|
||||
|
||||
`solana.ini` `[build]` `release_url=` (a URL or a local folder laid out like the release) and `levels=` override where it fetches from and which levels it builds, for testing.
|
||||
|
||||
## Making a release
|
||||
|
||||
```
|
||||
python release/make_release.py rac1 --decomp ../rac1-decomp --version 2026.10.13
|
||||
gh release create rac1 dist/rac1/rac1.json dist/rac1/rac1-build.zip dist/rac1/toolchain-win64.zip
|
||||
```
|
||||
|
||||
To update an existing game release, upload the new files over the old ones (`gh release upload rac1 dist/rac1/* --clobber`). Players get them with **Rebuild the game**.
|
||||
|
||||
The launcher itself goes in a release of its own (for example `launcher-v1`) with `Project Solana.exe`.
|
||||
|
||||
## Licence
|
||||
|
||||
TODO (maintainers): choose a licence for this repository. Third-party code keeps its own (see `THIRD_PARTY_NOTICES.md`).
|
||||
@@ -0,0 +1,60 @@
|
||||
# Third-party code
|
||||
|
||||
## pl_mpeg
|
||||
|
||||
`host/src/movie/pl_mpeg2.h` is pl_mpeg by Dominic Szablewski
|
||||
(https://github.com/phoboslab/pl_mpeg), extended here for MPEG-2. It is under the MIT licence below.
|
||||
|
||||
The MPEG-2 coefficient table B.15 and the run/level order in that file are
|
||||
data from ISO/IEC 13818-2, cross-checked against FFmpeg's
|
||||
`libavcodec/mpeg12data.c`.
|
||||
|
||||
### pl_mpeg licence (MIT)
|
||||
|
||||
Copyright (c) Dominic Szablewski - https://phoboslab.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
## jak-project's 989snd (ISC)
|
||||
|
||||
`host/src/rac1/snd989.c` and `host/src/rac1/snd989_tables.h` (the host's
|
||||
989snd IOP driver) follow the jak-project's reimplementation of 989snd
|
||||
(https://github.com/open-goal/jak-project, `game/sound/989snd` and
|
||||
`game/sound/common`, by Ziemas) for the sound-effect grain semantics, the LFOs,
|
||||
the volume and pan rules (MakeVolume, AdjustVolToGroup), the note-to-pitch
|
||||
conversion, and they take from it the note pitch table, the pan table and the
|
||||
SPU's Gaussian interpolation table. Those files are under the ISC licence:
|
||||
|
||||
Copyright (c) 2021-2024 Ziemas
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
The SPU2 reverb in the same file is written from the hardware's formula as
|
||||
documented in nocash's psx-spx; its preset register values are the PlayStation
|
||||
sound library's effect modes.
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)))
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -0,0 +1,49 @@
|
||||
# Project Solana launcher art (Direction D, "Horizon")
|
||||
|
||||
Every asset ships as SVG (`svg/`, render at build time) and as a PNG (`png/`, 32-bit RGBA, sRGB) at 3x the base size you gave. All text is converted to outlines, so the SVGs don't need any fonts installed.
|
||||
|
||||
| Asset | File | PNG size |
|
||||
|---|---|---|
|
||||
| App icon | `app_icon` | 1024x1024 |
|
||||
| App icon, 16/32 px version | `app_icon_small_16-32` | 1024x1024 |
|
||||
| Wordmark (mark + PROJECT + SOLANA) | `wordmark` | 780x120 |
|
||||
| Wordmark, no mark | `wordmark_text_only` | 780x120 |
|
||||
| Default banner | `default_banner` | 3840x720 |
|
||||
| Empty library art | `empty_library` | 960x600 |
|
||||
| Button icons (10) | `icons/*` | 60x60 |
|
||||
| Controller glyphs, white (22) | `controller_white/*` | 66x66 |
|
||||
| Controller glyphs, colour (22) | `controller_colour/*` | 66x66 |
|
||||
| Window background tile | `window_background_tile` | 512x512 (tileable, opaque) |
|
||||
|
||||
## Notes
|
||||
|
||||
- **App icon.** Use `app_icon` for the 48 px and larger sizes in the .ico, and `app_icon_small_16-32` for 16, 24 and 32 px. The small one drops the stripes, which turn to mush below 32 px, and has a thicker horizon.
|
||||
- **Banner.** The left 40% is plain background (#0A0E1A). The sun sits at x≈2900, and its top is at y≈250, inside the centred 400 px band. The horizon crosses at y≈470 to 520. Stars only appear right of x≈1700.
|
||||
- **Wordmark.** "PROJECT" is about 7 px tall at 100% scaling. If that's too small on your sidebar, use `wordmark_text_only` or just the mark.
|
||||
- **Icons.** Pure white (#FFFFFF) on transparent, with a 1.6 px stroke at 1x, so they're ready for your tinting. Play is filled.
|
||||
- **Controller glyph names:** `ps_cross`, `ps_circle`, `ps_square`, `ps_triangle`, `ps_l1`, `ps_r1`, `ps_l2`, `ps_r2`, `ps_start`, `ps_select`, `xbox_a`, `xbox_b`, `xbox_x`, `xbox_y`, `xbox_lb`, `xbox_rb`, `xbox_lt`, `xbox_rt`, `xbox_menu`, `xbox_view`.
|
||||
|
||||
## Palette (also in `palette.json`)
|
||||
|
||||
| Role | Hex |
|
||||
|---|---|
|
||||
| Background | `#0A0E1A` |
|
||||
| Sidebar | `#0F1424` |
|
||||
| Card | `#151C30` |
|
||||
| Card hover | `#1C2540` |
|
||||
| Border | `#26304D` |
|
||||
| Primary text | `#F4F6FB` |
|
||||
| Secondary text | `#9AA6C6` |
|
||||
| Accent | `#FF7A3D` |
|
||||
| Accent hover | `#FF9461` |
|
||||
| Text on accent | `#0A0E1A` (white on this orange fails contrast) |
|
||||
| Secondary accent (horizon cyan, optional) | `#7FD1FF` |
|
||||
| Status green | `#3DD68C` |
|
||||
| Status amber | `#FFB238` |
|
||||
| Status red | `#FF5A5F` |
|
||||
|
||||
## Font, corners, spacing
|
||||
|
||||
- **Font.** Keep Segoe UI for the UI itself. The display faces (Russo One for SOLANA, Rajdhani for PROJECT) are already baked into the art as outlines, and neither reads as well as Segoe at 12 to 14 px. Both are under the SIL OFL if you ever want to bundle one for headings. If you do, get the TTFs from Google Fonts.
|
||||
- **Corners.** 10 px on cards and 6 px on buttons and inputs. The tighter button radius suits the technical, hard-edged look better than 14 px.
|
||||
- **Spacing.** A 4 px grid: 16 px card padding, 12 px between cards, 24 px for page margins.
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"background": "#0A0E1A",
|
||||
"sidebar": "#0F1424",
|
||||
"card": "#151C30",
|
||||
"card_hover": "#1C2540",
|
||||
"border": "#26304D",
|
||||
"text_primary": "#F4F6FB",
|
||||
"text_secondary": "#9AA6C6",
|
||||
"accent": "#FF7A3D",
|
||||
"accent_hover": "#FF9461",
|
||||
"text_on_accent": "#0A0E1A",
|
||||
"accent_secondary": "#7FD1FF",
|
||||
"status_green": "#3DD68C",
|
||||
"status_amber": "#FFB238",
|
||||
"status_red": "#FF5A5F"
|
||||
}
|
||||
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 1.8 KiB |