This commit is contained in:
vetusmagnus
2026-10-13 20:04:31 -04:00
commit 28916eaf7d
208 changed files with 23516 additions and 0 deletions
+54
View File
@@ -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]);
}
+279
View File
@@ -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();
}
}
+278
View File
@@ -0,0 +1,278 @@
/* VU0/VU1 micro mode: which translated microprogram is in micro memory, and
* running it.
*
* The game uploads microcode with MPG (VIF0 for VU0, VIF1 for VU1). The bytes
* land in micro memory at their real address (VU0 0x11000000, 4 KB; VU1
* 0x11008000, 16 KB). tools/vu_recomp.py translated every microprogram image
* in the executable's .vutext to C at build time and kept its bytes: after an
* upload, every known upload block that overlaps it is compared with micro
* memory, so a start address (vcallms / MSCAL / MSCNT) maps to the image whose
* blocks are all in place. Microcode that matches nothing is reported once
* with its address and a hash (UYA_VUDUMP=dir also saves it), so it can be
* added to the build. */
#include "uya_host.h"
#include "uya_runtime.h"
#include "vu.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct { u32 vma, size; const u32 *words; } VuBlock;
typedef struct { const char *name; int vu; void (*fn)(RcVu0 *, u32); int nblk; const VuBlock *blk; } VuImage;
extern const VuImage vu_images[];
extern const int vu_image_count;
RcVu0 rc_vu1 = { .vf = { [0] = {0, 0, 0, 1} } };
#define MAX_IMG 64
static u8 blk_ok[MAX_IMG][16]; /* block b of image i is in micro memory */
static int cur_img[2][0x4000 / 8]; /* image last found for a start address, -1 none */
static int cache_valid[2];
static u8 *micro(int vu) { return (u8 *)(uintptr_t)(vu ? 0x11008000u : 0x11000000u); }
u32 vu_nonfinite; /* VU FMAC results that overflowed to inf/NaN (made +-MAX: vu_fc, vu.h) */
static u32 micro_size(int vu) { return vu ? 0x4000 : 0x1000; }
void vu_bad(int vu, u32 pc, const char *what)
{
static int n;
if (n++ < 32) uya_log("VU%d microprogram: %s at 0x%04X", vu, what, pc);
}
static u32 fnv(const u8 *p, u32 n) { u32 h = 2166136261u; while (n--) { h ^= *p++; h *= 16777619u; } return h; }
/* MPG: ninstr instructions to micro memory at byte address addr */
void vu_mpg(int vu, u32 addr, const u32 *w, u32 ninstr)
{
u8 *m = micro(vu);
u32 msz = micro_size(vu), bytes = ninstr * 8;
for (u32 i = 0; i < ninstr; i++) memcpy(m + ((addr + i * 8) & (msz - 1)), w + i * 2, 8);
/* re-check every known block that overlaps the upload */
int covered = 0;
for (int i = 0; i < vu_image_count && i < MAX_IMG; i++) {
const VuImage *im = &vu_images[i];
if (im->vu != vu) continue;
for (int b = 0; b < im->nblk && b < 16; b++) {
const VuBlock *bl = &im->blk[b];
if (bl->vma < addr + bytes && addr < bl->vma + bl->size) {
blk_ok[i][b] = memcmp(m + bl->vma, bl->words, bl->size) == 0;
if (blk_ok[i][b] && bl->vma <= addr && addr + bytes <= bl->vma + bl->size) covered = 1;
}
}
}
cache_valid[vu] = 0;
if (!covered) {
static u32 seen[64]; static int nseen;
u32 h = fnv((const u8 *)w, bytes);
int k;
for (k = 0; k < nseen && seen[k] != h; k++) ;
if (k == nseen) {
if (nseen < 64) seen[nseen++] = h;
uya_log("VU%d: microcode uploaded at 0x%04X (%u instructions, hash %08X) is not one the build translated", vu, addr, ninstr, h);
const char *dir = getenv("UYA_VUDUMP");
if (dir) {
char path[512]; snprintf(path, sizeof path, "%s/vu%d_%04X_%08X.bin", dir, vu, addr, h);
FILE *f = fopen(path, "wb"); if (f) { fwrite(w, 1, bytes, f); fclose(f); }
}
}
}
}
/* The image to run from pc: the one whose upload containing pc is in micro
* memory, preferring one that is there completely (a later upload may have
* replaced a part of an image the program at pc never reaches, e.g. image
* 28259's stub at 0x0000 under the 0x0000-0x0580 one). */
static int find_image(int vu, u32 pc)
{
if (!cache_valid[vu]) { memset(cur_img[vu], 0xFF, sizeof cur_img[vu]); cache_valid[vu] = 1; }
int *slot = &cur_img[vu][(pc & (micro_size(vu) - 1)) / 8];
if (*slot >= 0) return *slot;
int part = -1;
for (int i = 0; i < vu_image_count && i < MAX_IMG; i++) {
const VuImage *im = &vu_images[i];
if (im->vu != vu) continue;
int has = 0, ok = 1;
for (int b = 0; b < im->nblk && b < 16; b++) {
const VuBlock *bl = &im->blk[b];
if (pc >= bl->vma && pc < bl->vma + bl->size && blk_ok[i][b]) has = 1;
if (!blk_ok[i][b]) ok = 0;
}
if (has && ok) { *slot = i; return i; }
if (has && part < 0) part = i;
}
return *slot = part;
}
static unsigned long long runs[2], kicks, kick_qw;
static unsigned long long img_runs[MAX_IMG];
static u32 *pc_runs[MAX_IMG]; /* runs per start address, for the statistics */
static const char *cur_name = ""; static u32 cur_pc;
/* Testing: UYA_VUCAPTURE=image,n saves the state (start address, registers,
* data memory) before and after the n-th run of that image to
* vucap_before.bin / vucap_after.bin in the working directory, to replay it
* against a reference interpreter (tools/vu_refcheck.py). The file ends
* with VIF TOP and ITOP. */
static void capture(const char *path, int vu, u32 pc, const RcVu0 *v)
{
FILE *f = fopen(path, "wb");
if (!f) return;
fwrite(&pc, 4, 1, f); fwrite(v, sizeof *v, 1, f);
fwrite((void *)(uintptr_t)(vu ? 0x1100C000u : 0x11004000u), 1, vu ? 0x4000 : 0x1000, f);
u32 tops[2] = { vu_vif_top(vu), vu_vif_itop(vu) };
fwrite(tops, 4, 2, f);
fclose(f);
}
static void xgkick_flush(void);
unsigned long long uya_t_vu;
static void vu_run_(int vu, u32 pc);
extern int uya_timing; /* host/src/gs/gs.c: UYA_FPSLOG */
void vu_run(int vu, u32 pc)
{
if (uya_timing <= 0) { vu_run_(vu, pc); return; }
unsigned lo, hi, lo2, hi2; __asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));
vu_run_(vu, pc);
__asm__ volatile("rdtsc" : "=a"(lo2), "=d"(hi2));
uya_t_vu += (((unsigned long long)hi2 << 32) | lo2) - (((unsigned long long)hi << 32) | lo);
}
static void vu_run_(int vu, u32 pc)
{
RcVu0 *v = vu ? &rc_vu1 : &rc_vu0;
pc &= micro_size(vu) - 8;
int i = find_image(vu, pc);
if (i < 0) {
static u32 seen[2][0x4000 / 8 / 32];
u32 k = pc / 8;
if (!(seen[vu][k / 32] & (1u << (k % 32)))) {
seen[vu][k / 32] |= 1u << (k % 32);
uya_log("VU%d: no translated microprogram for start address 0x%04X", vu, pc);
}
return;
}
runs[vu]++;
img_runs[i]++;
if (!pc_runs[i]) pc_runs[i] = calloc(0x800, sizeof(u32));
if (pc_runs[i]) pc_runs[i][(pc / 8) & 0x7FF]++;
cur_name = vu_images[i].name; cur_pc = pc;
static int want = -2, seen; static char wname[32]; static unsigned wpc = 0xFFFFFFFFu;
if (want == -2) { const char *e = getenv("UYA_VUCAPTURE"); want = -1; if (e && sscanf(e, "%31[^,],%d,%x", wname, &want, &wpc) < 2) want = -1; }
if (want >= 0 && !strcmp(wname, vu_images[i].name) && (wpc == 0xFFFFFFFFu || wpc == pc) && seen++ == want) {
capture("vucap_before.bin", vu, pc, v);
vu_images[i].fn(v, pc);
if (vu) xgkick_flush();
capture("vucap_after.bin", vu, pc, v);
uya_log("VU capture of image %s run %d (from 0x%04X) saved", wname, want, pc);
return;
}
vu_images[i].fn(v, pc);
if (vu) xgkick_flush();
}
/* VU1 XGKICK: the GIF packet at quadword qaddr of VU1 data memory goes to the
* GS (PATH1). The packet is complete when the program kicks it. */
void uya_gif_packet(const u32 *data, u32 qwc);
static u32 kbuf[0x400 * 4], kbuf_n, kbuf_q;
void uya_vu_dump_kick(void)
{
void uya_vif1_dump_hist(void);
uya_log("current XGKICK: qw %03X, %u qwords, image %s from %04X; vi:", kbuf_q, kbuf_n, cur_name, cur_pc);
for (int k = 0; k < 16; k += 4) uya_log(" vi%-2d %04X %04X %04X %04X", k, rc_vu1.vi[k] & 0xFFFF, rc_vu1.vi[k + 1] & 0xFFFF, rc_vu1.vi[k + 2] & 0xFFFF, rc_vu1.vi[k + 3] & 0xFFFF);
for (u32 k = 0; k < kbuf_n && k < 48; k++) uya_log(" %03X: %08X %08X %08X %08X", (kbuf_q + k) & 0x3FF, kbuf[k * 4], kbuf[k * 4 + 1], kbuf[k * 4 + 2], kbuf[k * 4 + 3]);
uya_vif1_dump_hist();
}
/* XGKICK starts the transfer; the GIF reads the packet while the program
* goes on, and programs rely on that: they finish the packet in the kick's
* own branch delay slot or end-of-program slot (image 221571 writes the last
* GIF tag's EOP there). So a kick is sent when the next kick comes or the
* microprogram ends. */
static u32 kick_pending = 0xFFFFFFFFu;
static void xgkick_send(u32 qaddr);
void vu_xgkick(u32 qaddr)
{
if (kick_pending != 0xFFFFFFFFu) xgkick_send(kick_pending);
kick_pending = qaddr & 0x3FF;
}
static void xgkick_flush(void)
{
if (kick_pending != 0xFFFFFFFFu) { u32 q = kick_pending; kick_pending = 0xFFFFFFFFu; xgkick_send(q); }
}
static void xgkick_send(u32 qaddr)
{
u32 *buf = kbuf;
const u32 *mem = (const u32 *)(uintptr_t)0x1100C000u;
u32 q = qaddr & 0x3FF, n = 0;
while (n < 0x400) {
const u32 *t = mem + q * 4;
u64 lo = ((u64)t[1] << 32) | t[0];
u32 nloop = lo & 0x7FFF, eop = (lo >> 15) & 1, flg = (lo >> 58) & 3, nreg = (lo >> 60) & 15;
if (!nreg) nreg = 16;
u32 len = 1 + (flg == 0 ? nloop * nreg : flg == 1 ? (nloop * nreg + 1) / 2 : nloop);
for (u32 k = 0; k < len && n < 0x400; k++) { memcpy(buf + n * 4, mem + ((q + k) & 0x3FF) * 4, 16); n++; }
q = (q + len) & 0x3FF;
if (eop) break;
}
kicks++; kick_qw += n; kbuf_n = n; kbuf_q = qaddr & 0x3FF;
{
static int badleft = 2;
const u32 *t0 = mem + (qaddr & 0x3FF) * 4;
if (badleft > 0 && !(t0[0] | t0[1] | t0[2] | t0[3])) {
void uya_vif1_dump_hist(void);
badleft--;
uya_log("XGKICK of an empty GIF tag at qw %03X (image %s from %04X); vi:", qaddr & 0x3FF, cur_name, cur_pc);
for (int k = 0; k < 16; k += 4) uya_log(" vi%-2d %04X %04X %04X %04X", k, rc_vu1.vi[k] & 0xFFFF, rc_vu1.vi[k + 1] & 0xFFFF, rc_vu1.vi[k + 2] & 0xFFFF, rc_vu1.vi[k + 3] & 0xFFFF);
uya_vif1_dump_hist();
}
}
static int left = -1; /* testing: UYA_KICKLOG=n lists the first n packets */
if (left < 0) { const char *e = getenv("UYA_KICKLOG"); left = e ? atoi(e) : 0; }
if (left > 0) {
left--;
uya_log("XGKICK at qw 0x%03X, %u qwords, image %s from 0x%04X:", qaddr & 0x3FF, n, cur_name, cur_pc);
for (u32 k = 0; k < n && k < 24; k++) uya_log(" %08X %08X %08X %08X", buf[k * 4], buf[k * 4 + 1], buf[k * 4 + 2], buf[k * 4 + 3]);
}
{ /* "PATH1 <image>@<pc> kick <qw>" (snprintf's format, by hand: there are thousands of kicks a frame) */
extern const char *uya_gs_src; static char src[64];
static const char hx[] = "0123456789ABCDEF";
char *o = src; const char *nm = cur_name;
memcpy(o, "PATH1 ", 6); o += 6;
while (*nm && o < src + 40) *o++ = *nm++;
*o++ = '@';
for (int sh = 12; sh >= 0; sh -= 4) *o++ = hx[(cur_pc >> sh) & 15];
memcpy(o, " kick ", 6); o += 6;
for (int sh = 8; sh >= 0; sh -= 4) *o++ = hx[((qaddr & 0x3FF) >> sh) & 15];
*o = 0;
uya_gs_src = src;
}
uya_gif_packet(buf, n);
}
void vu_stats(void)
{
uya_log("VU microprograms run: VU0 %llu, VU1 %llu; XGKICK %llu (%llu qwords)", runs[0], runs[1], kicks, kick_qw);
for (int i = 0; i < vu_image_count && i < MAX_IMG; i++) {
if (!img_runs[i]) continue;
char line[256]; int o = 0;
for (int k = 0; k < 0x800 && o < 200; k++)
if (pc_runs[i] && pc_runs[i][k]) o += snprintf(line + o, sizeof line - o, " %04X:%u", k * 8, pc_runs[i][k]);
line[o] = 0;
uya_log(" image %s (VU%d): %llu runs, by start address%s", vu_images[i].name, vu_images[i].vu, img_runs[i], line);
}
}
/* the EE's vcallms / vcallmsr */
void rc_vcallms(RcCtx *c, u32 addr)
{
(void)c;
rc_vu0.susp = 0; /* a new program replaces a parked one */
vu_run(0, addr);
}
/* The EE wrote a VI register (ctc2): a VU0 program parked in a wait loop for
* it carries on until it ends or parks again. */
void rc_vu0_resume(void)
{
if (!rc_vu0.susp) return;
rc_vu0.susp = 0;
vu_run(0, rc_vu0.tpc);
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+56
View File
@@ -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
+193
View File
@@ -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
+647
View File
@@ -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;
}
}
+60
View File
@@ -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
+34
View File
@@ -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
+93
View File
@@ -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;
}
+4
View File
@@ -0,0 +1,4 @@
#ifdef __linux__
#include "uya_runtime.h"
int main(int argc, char **argv) { return uya_main(argc, argv); }
#endif
File diff suppressed because it is too large Load Diff
+73
View File
@@ -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;
}
+111
View File
@@ -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
+280
View File
@@ -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
+739
View File
@@ -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
+64
View File
@@ -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();
}
}
+359
View File
@@ -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;
}
+86
View File
@@ -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;
}
+152
View File
@@ -0,0 +1,152 @@
/* Runtime for the recompiled assembly-only functions (tools/recomp.py,
* host/include/rc.h): VU0 state, the stack the translated code runs on,
* calls by address and the few instructions that reach the host. */
#include "rc.h"
#include <stdlib.h>
#include <stdio.h>
void uya_log(const char *fmt, ...);
void uya_unimpl(const char *name);
uint32_t uya_frame_count(void);
RcVu0 rc_vu0 = { .vf = { [0] = {0, 0, 0, 1} } };
/* the translated code's stack: 1 MB of host memory (DMA from it works, see sdk/dma.c) */
static u8 rc_stack[1 << 20] __attribute__((aligned(64)));
u32 rc_sp = 0;
/* the x86 stack (see RC_QA in rc.h): from a little above the first call into
* translated code down 8 MB, never overlapping PS2 memory (if the stack lies
* below 32 MB, as it can on Windows, the exception is off) */
u32 rc_cstack_lo, rc_cstack_hi;
#include <stdlib.h>
/* testing: UYA_NANWATCH=addr (hex) stops the run (SIGILL, for a debugger) at the
* first entry into or dispatch from translated code that finds the float at addr
* NaN or infinite, and logs where the check before and this one were made */
static void nan_check(const char *where, u32 a)
{
static u32 w = 1; static void *prev; static u32 preva;
if (w == 1) { const char *e = getenv("UYA_NANWATCH"); w = e ? (u32)strtoul(e, 0, 16) : 0; }
if (!w) return;
u32 v = *(u32 *)(uintptr_t)w;
if ((v & 0x7F800000u) == 0x7F800000u) { uya_log("NANWATCH: %08X became %08X; previous check %p/%08X, now %s %p/%08X", w, v, prev, preva, where, __builtin_return_address(1), a); __builtin_trap(); }
prev = __builtin_return_address(1); preva = a;
}
void rc_enter(RcCtx *c)
{
nan_check("enter", 0);
if (!rc_cstack_hi) {
u32 here = (u32)(uintptr_t)__builtin_frame_address(0);
/* the x86 stack: above PS2 RAM (0-0x01FFFFFF), which Windows' main
* thread stack often is just above (0x03xxxxxx), Linux's far above */
if (here > 0x02100000u) {
rc_cstack_hi = here < 0xFFF00000u ? here + (1u << 20) : 0xFFFFFFFFu;
rc_cstack_lo = here - (8u << 20);
if (rc_cstack_lo < 0x02000000u) rc_cstack_lo = 0x02000000u;
} else rc_cstack_hi = 1; /* checked: empty range */
uya_log("x86 stack for translated code: %08X-%08X", rc_cstack_lo, rc_cstack_hi);
}
memset(c, 0, sizeof *c);
if (!rc_sp) rc_sp = (u32)(uintptr_t)(rc_stack + sizeof rc_stack - 64);
c->r[29].d[0] = (rc_sp - 64) & ~15u;
c->r[28].d[0] = RC_GP;
}
void rc_leave(RcCtx *c) { (void)c; }
typedef void (*rc_fn)(RcCtx *);
typedef struct { u32 addr; rc_fn fn; } rc_row;
extern const rc_row rc_table[];
extern const int rc_table_count;
void uya_trap_call(uint32_t ps2_addr, uint32_t ret_addr, const uint32_t *args);
/* The table in use: the executable's (rc_table) until ParseBin loads a level
* (rcNN_table, host/src/rac1/levels.c). A level's table has its own
* functions and thunks for the core functions it calls. */
static const rc_row *cur_table = rc_table;
static int cur_count = -1;
void rc_set_table(const rc_row *table, int count) { cur_table = table; cur_count = count; }
void rc_dispatch(RcCtx *c, u32 addr)
{
nan_check("dispatch", addr);
if (cur_count < 0) cur_count = rc_table_count;
int lo = 0, hi = cur_count - 1;
while (lo <= hi) {
int m = (lo + hi) / 2;
if (cur_table[m].addr == addr) { cur_table[m].fn(c); return; }
if (cur_table[m].addr < addr) lo = m + 1; else hi = m - 1;
}
u32 args[4] = { (u32)c->r[4].d[0], (u32)c->r[5].d[0], (u32)c->r[6].d[0], (u32)c->r[7].d[0] };
uya_trap_call(addr, (u32)c->r[31].d[0], args);
c->r[2].d[0] = 0;
}
/* rc_vcallms: host/src/gfx/vu.c (translated VU0 microprograms) */
void rc_syscall(RcCtx *c) { uya_log("recompiled code: syscall %d", (int)(s32)c->r[3].d[0]); }
void rc_break(u32 pc) { uya_log("recompiled code: break/trap at 0x%08X", pc); }
void rc_bad(u32 pc, const char *what)
{
static int n;
if (n++ < 64) uya_log("recompiled code: instruction %s at 0x%08X is not supported", what, pc);
}
u32 rc_cop0(int r)
{
uint32_t uya_frame_us(void);
if (r == 9) return (u32)((unsigned long long)uya_frame_count() * uya_frame_us() * 294912u / 1000u); /* Count: 294.912 MHz, one field per vsync (20 ms PAL) */
return 0;
}
/* testing: UYA_QUADCHECK=1 logs drawquad (func_001F7EF8) calls whose quad's
* GS words (+112: TEX0 at +120, TEX1 +128, ALPHA +136) are not what its
* callers put there, with the caller's return address */
void uya_dbg_quad(RcCtx *c)
{
static int on = -1, left = 30;
if (on < 0) on = getenv("UYA_QUADCHECK") != 0;
if (!on || left <= 0) return;
u32 a = (u32)c->r[4].d[0];
const u32 *q = (const u32 *)RC_QA(a + 112);
if (q[4] == 0x00000260 && q[5] == 0xFF90 && q[6] == 0x44 && q[7] == 0x80) return;
left--;
uya_log("quadcheck: drawquad(%08X) from ra %08X: GS words %08X%08X %08X%08X %08X%08X %08X%08X", a, (u32)c->r[31].d[0],
q[1], q[0], q[3], q[2], q[5], q[4], q[7], q[6]);
}
/* testing: functions named in UYA_RC_TRACE at build time call this on entry */
void rc_trace(RcCtx *c, const char *name)
{
/* UYA_RC_TRACE_MEM=addr,len[;addr,len...] (hex): memory shown with each call */
static char mem[256] = "?";
if (mem[0] == '?') { const char *e = getenv("UYA_RC_TRACE_MEM"); snprintf(mem, sizeof mem, "%s", e ? e : ""); }
char t[512]; int n = 0;
for (const char *p = mem; *p && n < 400;) {
unsigned a = 0, l = 0;
if (sscanf(p, "%x,%x", &a, &l) != 2) break;
n += snprintf(t + n, sizeof t - n, " [%X]", a);
for (unsigned i = 0; i < l && n < 480; i++) n += snprintf(t + n, sizeof t - n, "%02X", *(const u8 *)(uintptr_t)(a + i));
while (*p && *p != ';') p++;
if (*p == ';') p++;
}
t[n] = 0;
uya_log("v%u %s(%08X, %08X, %08X, %08X) ra %08X%s", uya_frame_count(), name, (u32)c->r[4].d[0], (u32)c->r[5].d[0],
(u32)c->r[6].d[0], (u32)c->r[7].d[0], (u32)c->r[31].d[0], t);
}
/* testing: a heuristic backtrace of translated code: the words on its stack
* (from rc_sp, which the thunks into host code set) that look like code
* addresses (return addresses the translated functions saved) */
const char *rc_stack_scan(void)
{
static char t[512];
int n = 0, k = 0;
const u32 *sp = (const u32 *)(uintptr_t)rc_sp;
t[0] = 0;
for (int i = 0; sp && i < 1024 && k < 12; i += 2) {
u32 v = sp[i];
if (v >= 0x00112380u && v < 0x00300000u && !(v & 3) && (v & 0xFFFF) && v != 0x00160000u && v != 0x00170000u) {
n += snprintf(t + n, sizeof t - n, " %08X@%d", v, i * 4); k++;
}
}
return t;
}
File diff suppressed because it is too large Load Diff
+307
View File
@@ -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},
};
+164
View File
@@ -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);
}
}
+128
View File
@@ -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;
}
+270
View File
@@ -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]);
}
+59
View File
@@ -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;
}
+171
View File
@@ -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; }
+124
View File
@@ -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;
}
+373
View File
@@ -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]);
}
+40
View File
@@ -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; }
+353
View File
@@ -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; }