shithub: fork

Download patch

ref: a25b7e46e00d7bb9769c1f48339322bd4a95bc7e
parent: aebec5673978f3b4cf6e84d7578a3bd3b5916421
author: qwx <qwx@sciops.net>
date: Mon Feb 24 13:15:41 EST 2025

add eui: experimental fixes and scaling

--- /dev/null
+++ b/sys/src/games/2600/tia.c
@@ -1,0 +1,195 @@
+#include <u.h>
+#include <libc.h>
+#include "../eui.h"
+#include "dat.h"
+#include "fns.h"
+
+int ppux=1, ppuy;
+int col, pri;
+u8int p0x, p1x, m0x, m1x, blx;
+u16int coll;
+u8int disp;
+int p0difc;
+int bwmod = 1<<3;
+
+enum {
+	SRCPF,
+	SRCP0,
+	SRCP1,
+	SRCM0,
+	SRCM1,
+	SRCBL,
+};
+
+static void
+pixeldraw(u8int v)
+{
+	u32int c;
+	union { u32int l; u8int c[4]; } u;
+	u32int *p;
+	static u32int col[] = {
+		0x000000, 0x404040, 0x6C6C6C, 0x909090, 0xB0B0B0, 0xC8C8C8, 0xDCDCDC, 0xECECEC, 
+		0x444400, 0x646410, 0x848424, 0xA0A034, 0xB8B840, 0xD0D050, 0xE8E85C, 0xFCFC68, 
+		0x702800, 0x844414, 0x985C28, 0xAC783C, 0xBC8C4C, 0xCCA05C, 0xDCB468, 0xECC878, 
+		0x841800, 0x983418, 0xAC5030, 0xC06848, 0xD0805C, 0xE09470, 0xECA880, 0xFCBC94, 
+		0x880000, 0x9C2020, 0xB03C3C, 0xC05858, 0xD07070, 0xE08888, 0xECA0A0, 0xFCB4B4, 
+		0x78005C, 0x8C2074, 0xA03C88, 0xB0589C, 0xC070B0, 0xD084C0, 0xDC9CD0, 0xECB0E0, 
+		0x480078, 0x602090, 0x783CA4, 0x8C58B8, 0xA070CC, 0xB484DC, 0xC49CEC, 0xD4B0FC, 
+		0x140084, 0x302098, 0x4C3CAC, 0x6858C0, 0x7C70D0, 0x9488E0, 0xA8A0EC, 0xBCB4FC, 
+		0x000088, 0x1C209C, 0x3840B0, 0x505CC0, 0x6874D0, 0x7C8CE0, 0x90A4EC, 0xA4B8FC, 
+		0x00187C, 0x1C3890, 0x3854A8, 0x5070BC, 0x6888CC, 0x7C9CDC, 0x90B4EC, 0xA4C8FC, 
+		0x002C5C, 0x1C4C78, 0x386890, 0x5084AC, 0x689CC0, 0x7CB4D4, 0x90CCE8, 0xA4E0FC, 
+		0x003C2C, 0x1C5C48, 0x387C64, 0x509C80, 0x68B494, 0x7CD0AC, 0x90E4C0, 0xA4FCD4, 
+		0x003C00, 0x205C20, 0x407C40, 0x5C9C5C, 0x74B474, 0x8CD08C, 0xA4E4A4, 0xB8FCB8, 
+		0x143800, 0x345C1C, 0x507C38, 0x6C9850, 0x84B468, 0x9CCC7C, 0xB4E490, 0xC8FCA4, 
+		0x2C3000, 0x4C501C, 0x687034, 0x848C4C, 0x9CA864, 0xB4C078, 0xCCD488, 0xE0EC9C, 
+		0x442800, 0x644818, 0x846830, 0xA08444, 0xB89C58, 0xD0B46C, 0xE8CC7C, 0xFCE08C, 
+	};
+	
+	c = col[v >> 1];
+	u.c[0] = c;
+	u.c[1] = c >> 8;
+	u.c[2] = c >> 16;
+	u.c[3] = 0xff;
+	p = (u32int *)pic + ppuy * PICW + ppux * 2;
+	p[0] = p[1] = u.l;
+}
+
+static void
+pixel(u8int v, int p, int s)
+{
+	if(p > pri){
+		col = v;
+		pri = p;
+	}
+	disp |= 1<<s;
+}
+
+static void
+playfield(void)
+{
+	int x, p;
+	u8int c;
+	
+	x = ppux / 4;
+	if(x >= 20)
+		if((reg[CTRLPF] & 1) != 0)
+			x = 39 - x;
+		else
+			x = x - 20;
+	if(x < 4){
+		if((reg[PF0] & 0x10<<x) == 0)
+			return;
+	}else if(x < 12){
+		if((reg[PF1] & 0x800>>x) == 0)
+			return;
+	}else
+		if((reg[PF2] & 1<<x-12) == 0)
+			return;
+	if((reg[CTRLPF] & 6) == 2)
+		if(ppux < 80){
+			c = reg[COLUP0];
+			p = 3;
+		}else{
+			c = reg[COLUP1];
+			p = 2;
+		}
+	else{
+		c = reg[COLUPF];
+		p = (reg[CTRLPF] & 4) + 1;
+	}
+	pixel(c, p, SRCPF);
+}
+
+static void
+player(int n)
+{
+	u8int c;
+	int x;
+
+	c = reg[GRP0 + n];
+	x = ppux - (n ? p1x : p0x);
+	if(x < 0)
+		return;
+	switch(reg[NUSIZ0 + n] & 7){
+	default: if(x >= 8) return; break;
+	case 1: if(x >= 8 && (x < 16 || x >= 24)) return; break;
+	case 2: if(x >= 8 && (x < 32 || x >= 40)) return; break;
+	case 3: if(x >= 40 || ((x & 15) >= 8)) return; break;
+	case 4: if(x >= 8 && (x < 64 || x >= 72)) return; break;
+	case 5: if(x >= 16) return; x >>= 1; break;
+	case 6: if(x >= 72 || ((x & 31) >= 8)) return; break;
+	case 7: if(x >= 32) return; x >>= 2; break;
+	}
+	x &= 7;
+	if((reg[REFP0 + n] & 8) == 0)
+		x ^= 7;
+	if((c & 1<<x) == 0)
+		return;
+	c = reg[COLUP0 + n];
+	pixel(c, 3 - n, SRCP0 + n);
+}
+
+static void
+missile(int n)
+{
+	int x;
+
+	x = ppux - (n ? m1x : m0x);
+	if((reg[RESMP0 + n] & 2) != 0){
+		if(n)
+			m1x = p1x;
+		else
+			m0x = p0x;
+		return;
+	}
+	if(x < 0 || x >= 1<<(reg[NUSIZ0] >> 4 & 3) || (reg[ENAM0 + n] & 2) == 0)
+		return;
+	pixel(reg[COLUP0 + n], 3 - n, SRCM0 + n);
+}
+
+static void
+ball(void)
+{
+	int x;
+
+	x = ppux - blx;
+	if(x < 0 || x >= 1<<(reg[CTRLPF] >> 4 & 3) || (reg[ENABL] & 2) == 0)
+		return;
+	pixel(reg[COLUPF], (reg[CTRLPF] & 4) + 1, SRCBL);
+}
+
+void
+tiastep(void)
+{
+	static u16int colltab[64] = {
+		0x0000, 0x0000, 0x0000, 0x0020, 0x0000, 0x0080, 0x8000, 0x80a0,
+		0x0000, 0x0200, 0x0001, 0x0221, 0x0002, 0x0282, 0x8003, 0x82a3,
+		0x0000, 0x0800, 0x0008, 0x0828, 0x0004, 0x0884, 0x800c, 0x88ac,
+		0x4000, 0x4a00, 0x4009, 0x4a29, 0x4006, 0x4a86, 0xc00f, 0xcaaf,
+		0x0000, 0x2000, 0x0010, 0x2030, 0x0040, 0x20c0, 0x8050, 0xa0f0,
+		0x0100, 0x2300, 0x0111, 0x2331, 0x0142, 0x23c2, 0x8153, 0xa3f3,
+		0x0400, 0x2c00, 0x0418, 0x2c38, 0x0444, 0x2cc4, 0x845c, 0xacfc,
+		0x4500, 0x6f00, 0x4519, 0x6f39, 0x4546, 0x6fc6, 0xc55f, 0xefff,
+	};
+
+	if(ppuy < PICH && ppux < 160){
+		col = reg[COLUBK];
+		pri = 0;
+		disp = 0;
+		playfield();
+		player(0);
+		player(1);
+		missile(0);
+		missile(1);
+		ball();
+		coll |= colltab[disp];
+		pixeldraw(col);
+	}
+	if(ppux == 160)
+		nrdy = 0;
+	if(++ppux == 228){
+		ppuy++;
+		ppux = 0;
+	}
+}
--- /dev/null
+++ b/sys/src/games/c64/vic.c
@@ -1,0 +1,369 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "../eui.h"
+#include "dat.h"
+#include "fns.h"
+
+int region, picidx;
+u16int ppux, ppuy, lastx, wrapx, maxy, lvis, rvis, uvis, dvis, picw, pich, lbord, rbord, ubord, dbord, spr0;
+u16int vc, vcbase, vmli;
+u8int badln, rc, displ, fract, visreg, hbord, vbord, rbord0, lbord0;
+u16int chrp[40];
+u64int pxs, npxs, npxs0, opxs;
+u8int fg;
+
+typedef struct spr spr;
+enum {
+	SPRFDMA = 1,
+	SPRFYEX = 2,
+	SPRFDISP = 4,
+};
+struct spr {
+	u8int flags;
+	u32int data;
+	u8int mc, mcbase, dp;
+	u16int x;
+} sp[8];
+
+void
+bordset(void)
+{
+	int r, c;
+
+	r = (reg[CTRL1] & RSEL) != 0;
+	c = (reg[CTRL2] & CSEL) != 0;
+	lbord = c ? 0x14 : 0x1c;
+	lbord0 = c ? 0xf0 : 0xe0;
+	rbord = c ? 0x154 : 0x14c;
+	rbord0 = c ? 0x0f : 0x3f;
+	ubord = r ? 0x33 : 0x37;
+	dbord = r ? 0xfb : 0xf7;
+	if((reg[CTRL1] & DEN) == 0)
+		ubord = -1;
+}
+
+void
+vicreset(void)
+{
+	switch(region){
+	case NTSC0:
+		lastx = 0x1fc;
+		wrapx = 0x19c;
+		maxy = 262;
+		picw = 412;
+		pich = 234;
+		spr0 = 0x16c;
+		lvis = 0x1e4;
+		rvis = 0x184;
+		uvis = 41;
+		dvis = 13;
+		break;
+	case NTSC:
+		lastx = 0x1fc;
+		wrapx = 0x19c;
+		maxy = 263;
+		picw = 419;
+		pich = 235;
+		spr0 = 0x174;
+		lvis = 0x1e4;
+		rvis = 0x18c;
+		uvis = 41;
+		dvis = 13;
+		break;
+	case PAL:
+		lastx = 0x1f4;
+		wrapx = 0x194;
+		maxy = 312;
+		picw = 404;
+		pich = 284;
+		spr0 = 0x164;
+		lvis = 0x1dc;
+		rvis = 0x17c;
+		uvis = 16;
+		dvis = 300;
+		break;
+	}
+	ppux = 4;
+	ppuy = 0;
+	bordset();
+}
+
+void
+pixeldraw(u64int p, int n)
+{
+	int i;
+	union { u8int c[4]; u32int l; } u;
+	static u8int cr[] = {0, 255, 136, 170, 204, 0, 0, 238, 221, 102, 255, 51, 119, 170, 0, 187};
+	static u8int cg[] = {0, 255, 0, 255, 68, 204, 0, 238, 136, 68, 119, 51, 119, 255, 136, 187};
+	static u8int cb[] = {0, 255, 0, 238, 204, 85, 170, 119, 85, 0, 119, 51, 119, 102, 255, 187};
+	u8int c;
+	u32int *q;
+
+	q = (u32int *)pic + picidx;
+	for(i = 0; i < n; i++){
+		c = p >> 56;
+		p <<= 8;
+		u.c[0] = cb[c];
+		u.c[1] = cg[c];
+		u.c[2] = cr[c];
+		u.c[3] = 0;
+		*q++ = u.l;
+	}
+	picidx += n;
+}
+
+void
+pixels(u8int d, u16int c)
+{
+	u8int c0, c1, c2, n;
+	int i;
+
+	npxs0 = npxs;
+	npxs = 0;
+	switch((reg[CTRL1] & (ECM|BMM) | reg[CTRL2] & MCM) >> 4){
+	case 0:
+		c0 = c >> 8;
+	normal:
+		fg = d;
+		for(i = 0; i < 8; i++){
+			npxs = npxs << 8 | ((d & 0x80) != 0 ? c0 : reg[BG0]);
+			d <<= 1;
+		}
+		break;
+	case 1:
+		c0 = c >> 8 & 7;
+		if((c & 0x800) == 0)
+			goto normal;
+		fg = d & 0xaa | d >> 1 & 0x55;
+		for(i = 0; i < 8; i += 2){
+			n = d >> 6;
+			npxs = npxs << 16 | (n == 3 ? c0 : reg[BG0 + n]) * 0x101;
+			d <<= 2;
+		}
+		break;
+	case 2:
+		c0 = c & 15;
+		c1 = c >> 4 & 15;
+		fg = d;
+		for(i = 0; i < 8; i++){
+			npxs = npxs << 8 | ((d & 0x80) != 0 ? c1 : c0);
+			d <<= 1;
+		}
+		break;
+	case 3:
+		c0 = c & 15;
+		c1 = c >> 4 & 15;
+		c2 = c >> 8;
+		fg = d & 0xaa | d >> 1 & 0x55;
+		for(i = 0; i < 8; i += 2){
+			n = d >> 6;
+			switch(n){
+			default: n = reg[BG0]; break;
+			case 1: n = c1; break;
+			case 2: n = c0; break;
+			case 3: n = c2;
+			}
+			npxs = npxs << 16 | n * 0x101;
+			d <<= 2;
+		}
+		break;
+	case 4:
+		c0 = c >> 8;
+		fg = d;
+		for(i = 0; i < 8; i++){
+			npxs = npxs << 8 | ((d & 0x80) != 0 ? c0 : reg[BG0 + (d >> 6 & 3)]);
+			d <<= 1;
+		}
+		break;
+	default:
+		fg = 0;
+		break;
+	}
+}
+
+void
+bgpixels(void)
+{
+	int i, j, x;
+	u8int h;
+	u8int spract, s, o;
+	
+	h = hbord;
+	opxs = 0;
+	for(i = 0; i < 8; i++){
+		if((reg[CTRL2] + 4 & 7) == i)
+			pxs = i >= 4 ? npxs : npxs0;
+		o = pxs >> 56;
+		pxs <<= 8;
+
+		x = ppux + i;
+		spract = 0;
+		for(j = 0; j < 8; j++)
+			if((sp[j].flags & SPRFDISP) != 0 && (u16int)(x-sp[j].x) < 48 && (sp[j].data & ((reg[SPRMC]&1<<j)?3:2)<<22) != 0)
+				spract |= 1<<j;
+		if((spract & spract - 1) != 0 && vbord == 0){
+			reg[SPRSPR] |= spract;
+			irq |= IRQSPRCOLL;
+		}
+		if(fg != 0 && spract != 0 && vbord == 0){
+			reg[SPRBG] |= spract;
+			irq |= IRQBGCOLL;
+		}
+		s = spract & ~(reg[SPRDP] & ((s8int)fg) >> 7);
+		if(s != 0)
+			for(j = 0; j < 8; j++)
+				if((s & 1<<j) != 0){
+					if((reg[SPRMC] & 1<<j) != 0)
+						switch(sp[j].data >> 22 & 3){
+						case 1: o = reg[SPRMC0]; break;
+						case 2: o = reg[SPRCOL+j]; break;
+						case 3: o = reg[SPRMC1]; break;
+						}
+					else
+						o = reg[SPRCOL+j];
+					break;
+				}
+		if((h & 0x80) != 0)
+			o = reg[EC];
+		opxs = opxs << 8 | o;
+		h <<= 1;
+		fg <<= 1;
+		for(j = 0; j < 8; j++)
+			if((u16int)(x-sp[j].x) < 48 && ((reg[SPRXE] & 1<<j) == 0 || (x-sp[j].x & 1) != 0))
+				if((reg[SPRMC] & 1<<j) != 0){
+					if((x-sp[j].x & 1) != 0)
+						sp[j].data <<= 2;
+				}else
+					sp[j].data <<= 1;
+	}
+}
+
+void
+vicstep(void)
+{
+	u16int gaddr;
+	int i;
+
+	if(ppuy == 0x30 && (reg[CTRL1] & DEN) != 0)
+		fract = 1;
+	badln = ((ppuy ^ reg[CTRL1]) & 7) == 0 && fract;
+	hbord = ((s8int)(hbord << 7)) >> 7;
+	if(ppux == rbord && hbord == 0)
+		hbord = rbord0;
+	else if(ppux == lbord){
+		if(ppuy == dbord)
+			vbord = 1;
+		if(ppuy == ubord)
+			vbord = 0;
+		if(!vbord)
+			hbord = lbord0;
+	}
+	if(badln)
+		displ = 1;
+	if(ppux == 4){
+		vc = vcbase;
+		vmli = 0;
+		if(badln)
+			rc = 0;
+	}
+	if(ppux == 12)
+		for(i = 0; i < 8; i++){
+			if((sp[i].flags & SPRFDISP) == 0 || (reg[SPRYE] & 1<<i) != 0 && (sp[i].flags & SPRFYEX) == 0)
+				continue;
+			sp[i].mcbase += 3;
+			if(sp[i].mcbase == 63)
+				sp[i].flags &= ~(SPRFDMA|SPRFDISP);
+		}
+	if(ppux >= 0x14 && ppux <= 0x14c){
+		if((reg[CTRL1] & BMM) != 0)
+			gaddr = (reg[MEMP] & 0x08) << 10 | vc << 3 | rc;
+		else
+			gaddr = (reg[MEMP] & 0x0e) << 10 | (chrp[vmli] & 0xff) << 3 | rc;
+		if(!displ)
+			gaddr = 0x3fff;
+		if((reg[CTRL1] & ECM) != 0)
+			gaddr &= ~0x600;
+		pixels(vmemread(gaddr) & -displ, chrp[vmli] & -displ);
+		vmli++;
+		vc = vc + 1 & 0x3ff;
+	}
+	if(visreg && (ppux >= lvis || ppux < rvis)){
+		bgpixels();
+		pixeldraw(opxs, ppux == lvis ? region == NTSC ? 3 : 4 : 8);
+	}
+	if(ppux == 0x14c){
+		for(i = 0; i < 8; i++){
+			if((reg[SPRYE] & 1<<i) != 0)
+				sp[i].flags ^= SPRFYEX;
+			if((reg[SPREN] & 1<<i) == 0 || reg[2*i+1] != (u8int)ppuy)
+				continue;
+			sp[i].flags |= SPRFDMA;
+			sp[i].mcbase = 0;
+			if((reg[SPRYE] & 1<<i) != 0)
+				sp[i].flags &= ~SPRFYEX;		
+		}
+	}else if(ppux == 0x154)
+		npxs = reg[BG0] * 0x0101010101010101ULL;
+	if(badln){
+		if(ppux == lastx - 8)
+			nrdy = 1;
+		else if(ppux >= 0xc && ppux <= 0x144)
+			chrp[vmli] = vmemread(vc | (reg[MEMP] & 0xf0) << 6) | cram[vc] << 8;
+		else if(ppux == 0x154)
+			nrdy = 0;
+	}
+	if(ppux == 0x164){
+		if(displ && rc == 7){
+			displ = badln;
+			vcbase = vc;
+		}
+		if(displ)
+			rc = rc + 1 & 7;
+		for(i = 0; i < 8; i++){
+			sp[i].mc = sp[i].mcbase;
+			if((sp[i].flags & SPRFDMA) != 0 && reg[2*i+1] == (u8int)ppuy)
+				sp[i].flags |= SPRFDISP;
+		}
+	}
+	if((u16int)(ppux - spr0) < 128){
+		i = ppux - spr0 >> 4;
+		nrdy = (sp[i].flags & SPRFDMA) != 0;
+		if((ppux & 8) == 0){
+			sp[i].dp = vmemread((reg[MEMP] & 0xf0) << 6 | 0x3f8 | i);
+			sp[i].x = nrdy ? reg[2 * i] | reg[MSBX] << 8 - i & 0x100 : -1;
+			if(nrdy)
+				sp[i].data = vmemread(sp[i].dp << 6 | sp[i].mc++) << 16;
+		}else if(nrdy){
+			sp[i].data = sp[i].data & 0xff00ff | vmemread(sp[i].dp << 6 | sp[i].mc++) << 8;
+			sp[i].data = sp[i].data & 0xffff00 | vmemread(sp[i].dp << 6 | sp[i].mc++);
+		}
+	}else if(ppux - spr0 == 128)
+		nrdy = 0;
+	if(ppux == wrapx){
+		ppuy++;
+		if(ppuy == maxy){
+			flush();
+			ppuy = 0;
+			vcbase = 0;
+		}
+		if((ppuy & 0xff) == reg[RASTER] && ((ppuy ^ reg[CTRL1] << 1) & 0x100) == 0)
+			irq |= IRQRASTER;
+		if(ppuy == dbord)
+			vbord = 1;
+		else if(ppuy == ubord)
+			vbord = 0;
+		else if(ppuy == dvis)
+			visreg = 0;
+		if(ppuy == uvis){
+			picidx = 0;
+			visreg = 1;
+		}
+		if(ppuy == 0xf7)
+			fract = 0;
+	}
+	if(ppux == lastx)
+		ppux = 4;
+	else
+		ppux += 8;
+}
--- /dev/null
+++ b/sys/src/games/eui.c
@@ -1,0 +1,479 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <keyboard.h>
+#include <mouse.h>
+#include "eui.h"
+
+typedef struct Kfn Kfn;
+
+u64int keys, keys2;
+Channel *keychan;
+int trace, paused;
+int savereq, loadreq;
+QLock pauselock;
+int scale, fixscale, warp10;
+uchar *pic;
+Rectangle picr;
+Mousectl *mc;
+Image *bg;
+
+static int profile, framestep;
+static int vwdx, vwdy, vwbpp;
+static ulong vwchan;
+static Image *fb;
+static Channel *conv, *sync[2];
+static uchar *screenconv[2], *backfb;
+static int screenconvi;
+
+struct Kfn{
+	Rune r;
+	int k;
+	char joyk[16];
+	void(*fn)(void);
+	Kfn *n;
+};
+static Kfn kfn, kkn;
+static int ax0, ax1;
+
+void *
+emalloc(ulong sz)
+{
+	void *v;
+
+	v = mallocz(sz, 1);
+	if(v == nil)
+		sysfatal("malloc: %r");
+	setmalloctag(v, getcallerpc(&sz));
+	return v;
+}
+
+Image *
+eallocimage(Rectangle r, ulong chan, int repl, ulong col)
+{
+	Image *i;
+
+	if((i = allocimage(display, r, chan, repl, col)) == nil)
+		sysfatal("allocimage: %r");
+	return i;
+}
+
+static void
+joyproc(void *)
+{
+	char *s, *down[9];
+	static char buf[64];
+	int n, k, j;
+	Kfn *kp;
+
+	j = 1;
+
+	for(;;){
+		n = read(0, buf, sizeof(buf) - 1);
+		if(n <= 0)
+			sysfatal("read: %r");
+		buf[n] = 0;
+		n = getfields(buf, down, nelem(down), 1, " ");
+		k = 0;
+		for(n--; n >= 0; n--){
+			s = down[n];
+			if(strcmp(s, "joy1") == 0)
+				j = 1;
+			else if(strcmp(s, "joy2") == 0)
+				j = 2;
+			for(kp=kkn.n; kp!=nil; kp=kp->n){
+				if(strcmp(kp->joyk, s) == 0)
+					k |= kp->k;
+			}
+		}
+		if(j == 2)
+			keys2 = k;
+		else{
+			keys = k;
+			if(keychan != nil)
+				nbsendul(keychan, k);
+		}
+	}
+}
+
+static void
+keyproc(void *)
+{
+	int fd, n, k;
+	static char buf[256];
+	char *s;
+	Rune r;
+	Kfn *kp;
+
+	fd = open("/dev/kbd", OREAD);
+	if(fd < 0)
+		sysfatal("open: %r");
+	for(;;){
+		if(buf[0] != 0){
+			n = strlen(buf)+1;
+			memmove(buf, buf+n, sizeof(buf)-n);
+		}
+		if(buf[0] == 0){
+			n = read(fd, buf, sizeof(buf)-1);
+			if(n <= 0)
+				sysfatal("read /dev/kbd: %r");
+			buf[n-1] = 0;
+			buf[n] = 0;
+		}
+		if(buf[0] == 'c'){
+			if(utfrune(buf, Kdel)){
+				close(fd);
+				threadexitsall(nil);
+			}
+			if(utfrune(buf, KF|5))
+				savereq = 1;
+			if(utfrune(buf, KF|6))
+				loadreq = 1;
+			if(utfrune(buf, KF|12))
+				profile ^= 1;
+			if(utfrune(buf, 't'))
+				trace = !trace;
+			for(kp=kfn.n; kp!=nil; kp=kp->n){
+				if(utfrune(buf, kp->r))
+					kp->fn();
+			}
+		}
+		if(buf[0] != 'k' && buf[0] != 'K')
+			continue;
+		s = buf + 1;
+		k = 0;
+		while(*s != 0){
+			s += chartorune(&r, s);
+			switch(r){
+			case Kdel: close(fd); threadexitsall(nil);
+			case Kesc:
+				if(paused)
+					qunlock(&pauselock);
+				else
+					qlock(&pauselock);
+				paused = !paused;
+				break;
+			case KF|1:	
+				if(paused){
+					qunlock(&pauselock);
+					paused=0;
+				}
+				framestep = !framestep;
+				break;
+			case '`':
+				warp10 = !warp10;
+				break;
+			}
+			for(kp=kkn.n; kp!=nil; kp=kp->n)
+				if(kp->r == r){
+					k |= kp->k;
+					break;
+				}
+		}
+		if((k & ax0) == ax0)
+			k &= ~ax0;
+		if((k & ax1) == ax1)
+			k &= ~ax1;
+		keys = k;
+		if(keychan != nil)
+			nbsendul(keychan, k);
+	}
+}
+
+static void
+timing(void)
+{
+	static int fcount;
+	static vlong old;
+	static char buf[32];
+	vlong new;
+
+	if(++fcount == 60)
+		fcount = 0;
+	else
+		return;
+	new = nsec();
+	if(new != old)
+		sprint(buf, "%6.2f%%", 1e11 / (new - old));
+	else
+		buf[0] = 0;
+	draw(screen, rectaddpt(Rect(10, 10, vwdx-40, 30), screen->r.min), bg, nil, ZP);
+	string(screen, addpt(screen->r.min, Pt(10, 10)), display->black, ZP, display->defaultfont, buf);
+	old = nsec();
+}
+
+static void
+screeninit(void)
+{
+	Point p;
+
+	send(sync[0], nil);
+	if(!fixscale){
+		scale = Dx(screen->r) / vwdx;
+		if(Dy(screen->r) / vwdy < scale)
+			scale = Dy(screen->r) / vwdy;
+	}
+	if(scale <= 0)
+		scale = 1;
+	else if(scale > 16)
+		scale = 16;
+	p = divpt(addpt(screen->r.min, screen->r.max), 2);
+	picr = Rpt(subpt(p, Pt(scale * vwdx/2, scale * vwdy/2)),
+		addpt(p, Pt(scale * vwdx/2, scale * vwdy/2)));
+	freeimage(fb);
+	fb = allocimage(display, Rect(0, 0, scale * vwdx, scale > 1 ? 1 : vwdy),
+		vwchan, scale > 1, 0);
+	free(backfb);
+	if(scale > 1)
+		backfb = emalloc(vwdx * vwbpp * scale);
+	else
+		backfb = nil;
+	draw(screen, screen->r, bg, nil, ZP);
+	recv(sync[1], nil);
+}
+
+void
+flushmouse(int discard)
+{
+	Mouse m;
+
+	if(nbrecvul(mc->resizec) > 0){
+		send(sync[0], nil);
+		if(getwindow(display, Refnone) < 0)
+			sysfatal("resize failed: %r");
+		recv(sync[1], nil);
+		screeninit();
+	}
+	if(discard)
+		while(nbrecv(mc->c, &m) > 0)
+			;
+}
+
+static void
+screenproc(void*)
+{
+	uchar *p;
+	enum { Draw, Sync1, Sync2 };
+	Alt alts[] = {
+		[Draw]	{.c = conv, .v = &p, .op = CHANRCV},
+		[Sync1]	{.c = sync[0], .op = CHANRCV},
+		[Sync2]	{.c = sync[1], .op = CHANNOP},
+		{.op = CHANEND},
+	};
+
+	for(;;) switch(alt(alts)){
+	case Draw:
+		if(scale == 1){
+			loadimage(fb, fb->r, p, vwdx * vwdy * vwbpp);
+			draw(screen, picr, fb, nil, ZP);
+		} else {
+			Rectangle r;
+			int w, x;
+			uchar *bp;
+
+			r = picr;
+			w = vwdx * vwbpp * scale;
+			while(r.min.y < picr.max.y){
+				x = 0;
+				bp = backfb;
+				while(x < vwdx){
+					switch(vwbpp){
+					case 4: {
+						u32int *d = (u32int *)bp;
+						u32int s = *(u32int *)p;
+						switch(scale){
+						case 16: *d++ = s;
+						case 15: *d++ = s;
+						case 14: *d++ = s;
+						case 13: *d++ = s;
+						case 12: *d++ = s;
+						case 11: *d++ = s;
+						case 10: *d++ = s;
+						case 9: *d++ = s;
+						case 8: *d++ = s;
+						case 7: *d++ = s;
+						case 6: *d++ = s;
+						case 5: *d++ = s;
+						case 4: *d++ = s;
+						case 3: *d++ = s;
+						case 2: *d++ = s;
+						default: *d++ = s;
+						}
+						bp = (uchar *)d;
+						break;
+					} case 2: {
+						u16int *d = (u16int *)bp;
+						u16int s = *(u16int *)p;
+						switch(scale){
+						case 16: *d++ = s;
+						case 15: *d++ = s;
+						case 14: *d++ = s;
+						case 13: *d++ = s;
+						case 12: *d++ = s;
+						case 11: *d++ = s;
+						case 10: *d++ = s;
+						case 9: *d++ = s;
+						case 8: *d++ = s;
+						case 7: *d++ = s;
+						case 6: *d++ = s;
+						case 5: *d++ = s;
+						case 4: *d++ = s;
+						case 3: *d++ = s;
+						case 2: *d++ = s;
+						default: *d++ = s;
+						}
+						bp = (uchar *)d;
+						break;
+					} case 1: {
+						uchar *d = bp;
+						uchar s = *p;
+						switch(scale){
+						case 16: *d++ = s;
+						case 15: *d++ = s;
+						case 14: *d++ = s;
+						case 13: *d++ = s;
+						case 12: *d++ = s;
+						case 11: *d++ = s;
+						case 10: *d++ = s;
+						case 9: *d++ = s;
+						case 8: *d++ = s;
+						case 7: *d++ = s;
+						case 6: *d++ = s;
+						case 5: *d++ = s;
+						case 4: *d++ = s;
+						case 3: *d++ = s;
+						case 2: *d++ = s;
+						default: *d++ = s;
+						}
+						bp = (uchar *)d;
+						break;
+					} default: {
+						int n;
+						uchar *s, *e, *d = bp;
+						for(n=scale; n>0; n--){
+							s = p;
+							e = d + vwbpp;
+							while(d < e)
+								*d++ = *s++;
+						}
+						bp = (uchar *)d;
+						break;
+					}}
+					p += vwbpp;
+					x++;
+				}
+				loadimage(fb, fb->r, backfb, w);
+				r.max.y = r.min.y+scale;
+				draw(screen, r, fb, nil, ZP);
+				r.min.y = r.max.y;
+			}
+		}
+		flushimage(display, 1);
+		break;
+	case Sync1:
+		alts[Draw].op = CHANNOP;
+		alts[Sync1].op = CHANNOP;
+		alts[Sync2].op = CHANSND;
+		break;
+	case Sync2:
+		alts[Draw].op = CHANRCV;
+		alts[Sync1].op = CHANRCV;
+		alts[Sync2].op = CHANNOP;
+		break;
+	}
+}
+
+void
+flushscreen(void)
+{
+	memmove(screenconv[screenconvi], pic, vwdx * vwdy * vwbpp);
+	if(sendp(conv, screenconv[screenconvi]) > 0)
+		screenconvi = (screenconvi + 1) % 2;
+	if(profile)
+		timing();
+}
+
+void
+flushaudio(int (*audioout)(void))
+{
+	static vlong old, delta;
+	vlong new, diff;
+
+	if(audioout == nil || audioout() < 0 && !warp10){
+		new = nsec();
+		diff = 0;
+		if(old != 0){
+			diff = BILLION/60 - (new - old) - delta;
+			if(diff >= MILLION)
+				sleep(diff/MILLION);
+		}
+		old = nsec();
+		if(diff > 0){
+			diff = (old - new) - (diff / MILLION) * MILLION;
+			delta += (diff - delta) / 100;
+		}
+	}
+	if(framestep){
+		paused = 1;
+		qlock(&pauselock);
+		framestep = 0;
+	}
+}
+
+void
+regkeyfn(Rune r, void (*fn)(void))
+{
+	Kfn *kp;
+
+	for(kp=&kfn; kp->n!=nil; kp=kp->n)
+		;
+	kp->n = emalloc(sizeof *kp);
+	kp->n->r = r;
+	kp->n->fn = fn;
+}
+
+void
+regkey(char *joyk, Rune r, int k)
+{
+	Kfn *kp;
+
+	for(kp=&kkn; kp->n!=nil; kp=kp->n)
+		;
+	kp->n = emalloc(sizeof *kp);
+	strncpy(kp->n->joyk, joyk, sizeof(kp->n->joyk)-1);
+	if(strcmp(joyk, "up") == 0 || strcmp(joyk, "down") == 0)
+		ax0 |= k;
+	if(strcmp(joyk, "left") == 0 || strcmp(joyk, "right") == 0)
+		ax1 |= k;
+	kp->n->r = r;
+	kp->n->k = k;
+}
+
+void
+initemu(int dx, int dy, int bpp, ulong chan, int dokey, void(*kproc)(void*))
+{
+	vwdx = dx;
+	vwdy = dy;
+	vwchan = chan;
+	vwbpp = bpp;
+	if(initdraw(nil, nil, nil) < 0)
+		sysfatal("initdraw: %r");
+	mc = initmouse(nil, screen);
+	if(mc == nil)
+		sysfatal("initmouse: %r");
+	if(dokey)
+		proccreate(kproc != nil ? kproc : keyproc, nil, mainstacksize);
+	if(kproc == nil)
+		proccreate(joyproc, nil, mainstacksize);
+	bg = eallocimage(Rect(0, 0, 1, 1), screen->chan, 1, 0xCCCCCCFF);
+	scale = fixscale;
+	conv = chancreate(sizeof(uchar*), 0);
+	sync[0] = chancreate(1, 0);
+	sync[1] = chancreate(1, 0);
+	proccreate(screenproc, nil, mainstacksize);
+	pic = emalloc(vwdx * vwdy * vwbpp);
+	screenconv[0] = emalloc(vwdx * vwdy * vwbpp);
+	screenconv[1] = emalloc(vwdx * vwdy * vwbpp);
+	screeninit();
+}
--- /dev/null
+++ b/sys/src/games/eui.h
@@ -1,0 +1,19 @@
+enum{
+	MILLION = 1000000,
+	BILLION = 1000000000,
+};
+
+extern u64int keys, keys2;
+extern int trace, paused;
+extern int savereq, loadreq;
+extern QLock pauselock;
+extern int scale, fixscale, warp10;
+extern uchar *pic;
+
+void*	emalloc(ulong);
+void	flushmouse(int);
+void	flushscreen(void);
+void	flushaudio(int(*)(void));
+void	regkeyfn(Rune, void(*)(void));
+void	regkey(char*, Rune, int);
+void	initemu(int, int, int, ulong, int, void(*)(void*));
--- /dev/null
+++ b/sys/src/games/gb/ppu.c
@@ -1,0 +1,326 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "../eui.h"
+#include "dat.h"
+#include "fns.h"
+
+u8int ppustate, ppuy, ppuw;
+ulong hblclock, rendclock;
+jmp_buf mainjmp, renderjmp;
+static int cyc, done, ppux, ppux0;
+extern int prish;
+extern u32int white;
+
+Var ppuvars[] = {VAR(ppustate), VAR(ppuy), VAR(hblclock), VAR(rendclock), {nil, 0, 0}};
+
+#define ryield() {if(setjmp(renderjmp) == 0) longjmp(mainjmp, 1);}
+#define myield() {if(setjmp(mainjmp) == 0) longjmp(renderjmp, 1);}
+
+typedef struct sprite sprite;
+struct sprite {
+	u8int dy, x, t;
+	u8int fetched, pal;
+	u16int chr;
+};
+enum {
+	SPRPRI = 0x80,
+	SPRYFL = 0x40,
+	SPRXFL = 0x20,
+	SPRPAL = 0x10,
+	SPRBANK = 0x08,
+	
+	TILCOL0 = 0x01,
+	TILPRI = 0x02,
+	TILSPR = 0x04,
+};
+sprite spr[10], *sprm;
+
+void
+ppurender(void)
+{
+	int x, y, i, n, m, win;
+	u16int ta, ca, chr;
+	u8int tile, attr, pali;
+	u32int sr[8], *picp;
+	#define eat(nc) if(cyc <= nc){for(i = 0; i < nc; i++) if(--cyc == 0) ryield();} else cyc -= nc;
+	
+	ryield();
+	
+	attr = 0;
+	for(;;){
+		eat(6*2);
+		m = 168 + (reg[SCX] & 7);
+		win = 0;
+		if((reg[LCDC] & WINEN) != 0 && ppuy >= reg[WY] && reg[WX] <= 166)
+			if(reg[WX] == 0)
+				m = 7;
+			else if(reg[WX] == 166)
+				m = 0;
+			else{
+				m = reg[WX] + (reg[SCX] & 7) + 1;
+				win = -1;
+			}
+		ppux = 0;
+		ppux0 = 0;
+		picp = (u32int*)pic + ppuy * PICW;
+		y = ppuy + reg[SCY] << 1 & 14;
+		ta = 0x1800 | reg[LCDC] << 7 & 0x400 | ppuy + reg[SCY] << 2 & 0x3e0 | reg[SCX] >> 3;
+		x = -(reg[SCX] & 7);
+	restart:
+		do{
+			tile = vram[ta];
+			if((mode & COL) != 0)
+				attr = vram[8192 + ta];
+			if((reg[LCDC] & BGTILE) != 0)
+				ca = (tile << 4) + y;
+			else
+				ca = 0x1000 + ((s32int)(tile << 24) >> 20) + y;
+			if((attr & 0x40) != 0)
+				ca ^= 14;
+			ca |= (attr & 8) << 10;
+			chr = vram[ca] << 8 | vram[ca+1];
+			pali = attr << 2 & 0x1c;
+			if((attr & 0x20) == 0)
+				for(i = 0; i < 8; i++){
+					sr[i] = pal[pali | chr >> 15 | chr >> 6 & 2] | ((chr & 0x8080) == 0) << prish;
+					chr <<= 1;
+				}
+			else
+				for(i = 0; i < 8; i++){
+					sr[i] = pal[pali | chr << 1 & 2 | chr >> 8 & 1] | ((chr & 0x0101) == 0) << prish;
+					chr >>= 1;
+				}
+			if((attr & 0x80) != 0)
+				for(i = 0; i < 8; i++)
+					sr[i] |= 2 << prish;
+			if((reg[LCDC] & BGEN) == 0 && (mode & COL) == 0 && ((mode & CGB) != 0 || win == 0))
+				for(i = 0; i < 8; i++)
+					sr[i] = white;
+			if(cyc <= 2*8){
+				for(i = 0; i < 2*8; i++)
+					if(--cyc == 0)
+						ryield();
+				y = ppuy + reg[SCY] << 1 & 14;
+				ta = 0x1800 | reg[LCDC] << 7 & 0x400 | ppuy + reg[SCY] << 2 & 0x3e0 | ta & 0x1f;
+			}else
+				cyc -= 2*8;
+			m -= 8;
+			n = m < 8 ? m : 8;
+			if((ta & 0x1f) == 0x1f)
+				ta &= ~0x1f;
+			else
+				ta++;
+			for(i = 0; i < n; i++, x++)
+				if(x >= 0)
+					picp[x] = sr[i];
+			ppux = x;
+		}while(m > 8);
+		if(win == -1){
+			win = 1;
+			ta = 0x1800 | reg[LCDC] << 4 & 0x400 | ppuw - reg[WY] << 2 & 0x3e0;
+			y = ppuw - reg[WY] << 1 & 14;
+			cyc += 2;
+			m = 175 - reg[WX];
+			goto restart;
+		}
+		done = 1;
+		ryield();
+	}
+}
+
+void
+oamsearch(void)
+{
+	u8int *p;
+	sprite *q;
+	int y0, sy;
+	u8int t, tn;
+	u8int *chrp;
+	
+	y0 = ppuy + 16;
+	sy = (reg[LCDC] & SPR16) != 0 ? 16 : 8;
+	sprm = spr;
+	if((reg[LCDC] & SPREN) == 0)
+		return;
+	for(p = oam; p < oam + 160; p += 4){
+		if((u8int)(y0 - p[0]) >= sy)
+			continue;
+		if((mode & COL) == 0){
+			for(q = spr; q < sprm; q++)
+				if(q->x > p[1]){
+					if(sprm != spr + 10){
+						memmove(q + 1, q, (sprm - q) * sizeof(sprite));
+						sprm++;
+					}else
+						memmove(q + 1, q, (sprm - 1 - q) * sizeof(sprite));
+					goto found;
+				}
+			if(q == spr + 10)
+				continue;
+			sprm++;
+		found:;
+		}else
+			q = sprm++;
+		q->dy = y0 - p[0];
+		q->x = p[1];
+		q->t = t = p[3];
+		if((t & SPRYFL) != 0)
+			q->dy ^= sy - 1;
+		tn = p[2];
+		if(sy == 16)
+			tn = tn & ~1 | q->dy >> 3;
+		chrp = vram + (tn << 4 | q->dy << 1 & 14);
+		if((mode & COL) != 0){
+			chrp += t << 10 & 0x2000;
+			q->pal = 0x20 | t << 2 & 0x1c;
+		}else
+			q->pal = 4 + (t >> 2 & 4);
+		q->chr = chrp[0] << 8 | chrp[1];
+		if(p[1] < 8)
+			if((t & SPRXFL) != 0)
+				q->chr >>= 8 - p[1];
+			else
+				q->chr <<= 8 - p[1];
+		q->fetched = 0;
+		if((mode & COL) != 0 && sprm == spr + 10)
+			break;
+	}
+}
+
+void
+sprites(void)
+{
+	sprite *q;
+	u8int attr;
+	u32int *picp;
+	int x, x1;
+	u16int chr;
+	
+	picp = (u32int*)pic + ppuy * PICW;
+	for(q = spr; q < sprm; q++){
+		if(q->x <= ppux0 || q->x >= ppux + 8)
+			continue;
+		x = q->x - 8;
+		if(x < ppux0) x = ppux0;
+		x1 = q->x;
+		if(x1 > ppux) x1 = ppux;
+		for(; x < x1; x++){
+			attr = picp[x] >> prish;
+			chr = q->chr;
+			if((chr & ((q->t & SPRXFL) != 0 ? 0x0101 : 0x8080)) != 0 && (attr & TILSPR) == 0 &&
+					((mode & COL) != 0 && (reg[LCDC] & BGPRI) == 0 ||
+					(attr & TILCOL0) != 0 ||
+					(attr & TILPRI) == 0 && (q->t & SPRPRI) == 0))
+				if((q->t & SPRXFL) == 0)
+					picp[x] = pal[q->pal | chr >> 15 | chr >> 6 & 2] | TILSPR << prish;
+				else
+					picp[x] = pal[q->pal | chr << 1 & 2 | chr >> 8 & 1] | TILSPR << prish;
+			if((q->t & SPRXFL) != 0)
+				q->chr >>= 1;
+			else
+				q->chr <<= 1;
+		}
+	}
+	ppux0 = ppux;
+}
+
+void
+ppusync(void)
+{
+	if(ppustate != 3)
+		return;
+	cyc = clock - rendclock;
+	if(cyc != 0)
+		myield();
+	sprites();
+	rendclock = clock;
+}
+
+int
+linelen(void)
+{
+	int t;
+	
+	t = 174 + (reg[SCX] & 7);
+	if((reg[LCDC] & WINEN) != 0 && ppuy >= reg[WY] && reg[WX] < 166)
+		if(reg[WX] == 0)
+			t += 7;
+		else
+			t += 6;
+	return t*2;
+}
+
+void
+hblanktick(void *)
+{
+	extern Event evhblank;
+	int t;
+	
+	switch(ppustate){
+	case 0:
+		hblclock = clock + evhblank.time;
+		if(reg[WX] <= 166 && reg[WY] <= 143)
+			ppuw++;
+		if(++ppuy == 144){
+			ppustate = 1;
+			if((reg[STAT] & IRQM1) != 0)
+				reg[IF] |= IRQLCDS;
+			addevent(&evhblank, 456*2);
+			reg[IF] |= IRQVBL;
+			flush();
+		}else{
+			ppustate = 2;
+			if((reg[STAT] & IRQM2) != 0)
+				reg[IF] |= IRQLCDS;
+			addevent(&evhblank, 80*2);
+		}
+		if((reg[STAT] & IRQLYC) != 0 && ppuy == reg[LYC])
+			reg[IF] |= IRQLCDS;
+		break;
+	case 1:
+		hblclock = clock + evhblank.time;
+		if(reg[WX] <= 166 && reg[WY] <= 143)
+			ppuw++;
+		if(++ppuy == 154){
+			ppuy = 0;
+			ppuw = 0;
+			ppustate = 2;
+			if((reg[STAT] & IRQM2) != 0)
+				reg[IF] |= IRQLCDS;
+			addevent(&evhblank, 80*2);
+		}else
+			addevent(&evhblank, 456*2);
+		if((reg[STAT] & IRQLYC) != 0 && ppuy == reg[LYC])
+			reg[IF] |= IRQLCDS;
+		break;
+	case 2:
+		oamsearch();
+		rendclock = clock + evhblank.time;
+		ppustate = 3;
+		addevent(&evhblank, linelen());
+		break;
+	case 3:
+		ppusync();
+		if(!done) print("not done?!\n");
+		done = 0;
+		ppustate = 0;
+		if((reg[STAT] & IRQM0) != 0)
+			reg[IF] |= IRQLCDS;
+		t = hblclock + 456 * 2 - clock;
+		addevent(&evhblank, t < 0 ? 456 * 2 : t);
+		if(dma & DMAHBLANK)
+			dma |= DMAREADY;
+		break;
+	}
+}
+
+void
+ppuinit(void)
+{
+	static char ppustack[4096];
+	
+	renderjmp[JMPBUFPC] = (uintptr)ppurender;
+	renderjmp[JMPBUFSP] = (uintptr)(ppustack + sizeof(ppustack) - 64);
+	myield();
+}
--- /dev/null
+++ b/sys/src/games/gba/apu.c
@@ -1,0 +1,390 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "../eui.h"
+#include "dat.h"
+#include "fns.h"
+
+Event evsamp;
+int srate, sratediv;
+s16int sbuf[2*4000], *sbufp, bias;
+enum {
+	Freq = 44100,
+};
+static int stime;
+s8int snddma[2];
+static int fd;
+
+u16int envctr, envrel, envmod;
+u8int sweepen, sweepctr;
+u16int sweepfreq;
+typedef struct chan chan;
+struct chan {
+	u8int n, ectr;
+	u16int len;
+	u16int *env, *freq;
+	u16int fctr, fthr;
+	u32int finc;
+	u8int vol;
+};
+u8int wave[64], wpos, wbank;
+u16int lfsr;
+
+chan sndch[4] = {
+	{
+		.n = 0,
+		.env = reg + 0x62/2,
+		.freq = reg + 0x64/2,
+	},
+	{
+		.n = 1,
+		.env = reg + 0x68/2,
+		.freq = reg + 0x6c/2,
+	},
+	{
+		.n = 2,
+	},
+	{
+		.n = 3,
+		.env = reg + 0x78/2,
+		.freq = reg + 0x7c/2,
+	}
+};
+
+Var apuvars[] = {
+	ARR(snddma), VAR(envctr), VAR(envrel), VAR(envmod), VAR(sweepen),
+	VAR(sweepctr), VAR(sweepfreq), ARR(wave), VAR(wpos), VAR(wbank), VAR(lfsr),
+	VAR(sndch[0].ectr), VAR(sndch[0].len), VAR(sndch[0].fctr), VAR(sndch[0].fthr), VAR(sndch[0].finc), VAR(sndch[0].vol),
+	VAR(sndch[1].ectr), VAR(sndch[1].len), VAR(sndch[1].fctr), VAR(sndch[1].fthr), VAR(sndch[1].finc), VAR(sndch[1].vol),
+	VAR(sndch[2].ectr), VAR(sndch[2].len), VAR(sndch[2].fctr), VAR(sndch[2].fthr), VAR(sndch[2].finc), VAR(sndch[2].vol),
+	VAR(sndch[3].ectr), VAR(sndch[3].len), VAR(sndch[3].fctr), VAR(sndch[3].fthr), VAR(sndch[3].finc), VAR(sndch[3].vol),
+	{nil, 0, 0},
+};
+
+void
+rate(int i, u16int v)
+{
+	switch(i){
+	case 0: case 1:
+		sndch[i].finc = 131072ULL * 65536 / (srate * (2048 - (v & 0x7ff)));
+		break;
+	case 2:
+		sndch[2].finc = 2097152ULL * 65536 / (srate * (2048 - (v & 0x7ff)));
+		break;
+	case 3:
+		sndch[3].finc = 524288ULL * 65536 / srate;
+		if((v & 7) != 0)
+			sndch[3].finc /= v & 7;
+		else
+			sndch[3].finc <<= 1;
+		sndch[3].finc >>= (v >> 4 & 15) + 1;
+	}
+}
+
+void
+env(chan *c)
+{
+	if((envmod & 1) == 0 && c->len > 0 && (*c->freq & 1<<14) != 0)
+		--c->len;
+	if(c->len == 0){
+		c->vol = 0;
+		return;
+	}
+	if((envmod & 7) != 7 || c->ectr == 0 || --c->ectr != 0)
+		return;
+	c->ectr = *c->env >> 8 & 7;
+	if((*c->env & 1<<11) != 0){
+		if(c->vol < 15)
+			c->vol++;
+	}else
+		if(c->vol > 0)
+			c->vol--;
+}
+
+s8int
+wavesamp(void)
+{
+	s8int x;
+	u16int vol, cnt;
+	int v;
+
+	sndch[2].fctr = v = sndch[2].fctr + sndch[2].finc;
+	if(sndch[2].len == 0 || (reg[0x70/2] & 1<<7) == 0)
+		return 0;
+	vol = reg[0x72/2];
+	cnt = reg[0x70/2];
+	for(;;){
+		x = wave[wbank ^ wpos];
+		v -= 0x10000;
+		if(v < 0)
+			break;
+		wpos++;
+		if((cnt & 1<<5) != 0)
+			wpos &= 63;
+		else
+			wpos &= 31;
+	}
+	if((vol & 1<<15) != 0)
+		x = (x >> 2) + (x >> 3);
+	else if((vol & 3<<14) == 0)
+		x = 0;
+	else
+		x = x >> (vol >> 14 & 3);
+	return x;
+}
+
+s8int
+lfsrsamp(void)
+{
+	int v;
+	u16int l;
+
+	sndch[3].fctr = v = sndch[3].fctr + sndch[3].finc;
+	for(;;){
+		l = lfsr;
+		v -= 0x10000;
+		if(v < 0)
+			break;
+		lfsr >>= 1;
+		if(((l ^ lfsr) & 1) != 0)
+			if((reg[0x7c/2] & 1<<3) != 0)
+				lfsr |= 0x40;
+			else
+				lfsr |= 0x4000;
+	}
+	if((l & 1) != 0)
+		return -sndch[3].vol;
+	else
+		return sndch[3].vol;
+}
+
+void
+sweep(int wb)
+{
+	u16int fr;
+	int d;
+	u16int cnt;
+	
+	cnt = reg[0x60/2];
+	d = sweepfreq >> (cnt & 7);
+	if((cnt & 1<<3) != 0)
+		d = -d;
+	fr = sweepfreq + d;
+	if(fr > 2047){
+		sndch[0].len = 0;
+		sndch[0].vol = 0;
+		sweepen = 0;
+	}else if(wb){
+		sweepfreq = fr;
+		reg[0x64/2] = reg[0x64/2] & 0xfc00 | fr;
+		rate(0, fr);
+		sweep(0);
+	}
+}
+
+void
+sndstart(chan *c, u16int v)
+{
+	u16int cnt;
+
+	c->vol = *c->env >> 12;
+	c->ectr = *c->env >> 8 & 7;
+	if(c->len == 0)
+		c->len = 64;
+	if(c == sndch){
+		cnt = reg[0x60/2];
+		sweepen = (cnt & 0x07) != 0 && (cnt & 0x70) != 0;
+		sweepctr = cnt >> 4 & 7;
+		sweepfreq = v & 0x7ff;
+		if((cnt & 0x07) != 0)
+			sweep(0);
+	}
+}
+
+void
+sampletick(void *)
+{
+	u16int cntl, cnth;
+	s16int ch[6];
+	s16int s[2];
+	int i;
+	
+	addevent(&evsamp, sratediv);
+	
+	if(--envctr == 0){
+		envctr = envrel;
+		env(&sndch[0]);
+		env(&sndch[1]);
+		if((envmod & 1) == 0 && sndch[2].len > 0 && (reg[0x74/2] & 1<<14) != 0)
+			sndch[2].len--;
+		env(&sndch[3]);
+		if((envmod & 3) == 2 && sweepen && --sweepctr == 0){
+			sweepctr = reg[0x60/2] >> 4 & 7;
+			sweep(1);
+		}
+		envmod++;
+	}
+	
+	sndch[0].fctr += sndch[0].finc;
+	if(sndch[0].fctr >= sndch[0].fthr)
+		ch[0] = sndch[0].vol;
+	else
+		ch[0] = -sndch[0].vol;
+	sndch[1].fctr += sndch[1].finc;
+	if(sndch[1].fctr >= sndch[1].fthr)
+		ch[1] = sndch[1].vol;
+	else
+		ch[1] = -sndch[1].vol;
+	ch[2] = wavesamp();
+	ch[3] = lfsrsamp();
+	
+	cntl = reg[SOUNDCNTL];
+	cnth = reg[SOUNDCNTH];
+	for(i = 0; i < 4; i++)
+		ch[i] = ch[i] >> (cnth & 3);
+	ch[5] = snddma[0] << 1 + (cnth >> 2 & 1);
+	ch[6] = snddma[1] << 1 + (cnth >> 3 & 1);
+	
+	s[0] = 0;
+	s[1] = 0;
+	for(i = 0; i < 4; i++){
+		if((cntl & 1<<8<<i) != 0)
+			s[1] += ch[i] * (1 + (cntl & 7));
+		if((cntl & 1<<12<<i) != 0)
+			s[0] += ch[i] * (1 + (cntl >> 4 & 7));
+	}
+	for(i = 5; i < 6; i++){
+		if((cnth & 1<<3<<i) != 0)
+			s[1] += ch[i];
+		if((cnth & 1<<4<<i) != 0)
+			s[0] += ch[i]; 
+	}
+	s[0] += bias;
+	s[1] += bias;
+	if(s[0] < -0x200) s[0] = -0x200;
+	else if(s[0] > 0x1ff) s[0] = 0x1ff;
+	if(s[1] < -0x200) s[1] = -0x200;
+	else if(s[1] > 0x1ff) s[1] = 0x1ff;
+	
+	stime -= Freq;
+	while(stime < 0){
+		if(sbufp < sbuf + nelem(sbuf)){
+			sbufp[0] = s[0] << 6;
+			sbufp[1] = s[1] << 6;
+			sbufp += 2;
+		}
+		stime += srate;
+	}
+}
+
+
+void
+sndwrite(u16int a, u16int v)
+{
+	int sh, p, i;
+	static u16int thr[4] = {0x2000, 0x4000, 0x8000, 0xC000};
+	
+	switch(a){
+	case 0x62:
+		sndch[0].fthr = thr[v >> 6 & 3];
+		sndch[0].len = 64 - (v & 63);
+		break;
+	case 0x64:
+		rate(0, v);
+		if((v & 1<<15) != 0)
+			sndstart(&sndch[0], v);
+		break;
+	case 0x68:
+		sndch[1].fthr = thr[v >> 6 & 3];
+		break;
+	case 0x6c:
+		rate(1, v);
+		if((v & 1<<15) != 0)
+			sndstart(&sndch[1], v);
+		break;
+	case 0x70:
+		wbank = v >> 1 & 32;
+		break;
+	case 0x72:
+		sndch[2].len = 256 - (v & 0xff);
+		break;
+	case 0x74:
+		rate(2, v);
+		if((v & 1<<15) != 0 && sndch[2].len == 0)
+			sndch[2].len = 256;
+		break;
+	case 0x7c:
+		rate(3, v);
+		if((v & 1<<15) != 0){
+			if((v & 1<<3) != 0)
+				lfsr = 0x7f;
+			else
+				lfsr = 0x7fff;
+			sndstart(&sndch[3], v);
+		}
+		break;
+	case SOUNDBIAS*2:
+		sh = 9 - (v >> 14 & 3);
+		if(sratediv != 1<<sh){
+			srate = 1 << 24 - sh;
+			sratediv = 1 << sh;
+			envrel = srate / 512;
+			rate(0, reg[0x64/2]);
+			rate(1, reg[0x6c/2]);
+			rate(2, reg[0x74/2]);
+			rate(3, reg[0x7c/2]);
+		}
+		bias = (v & 0x3ff) - 0x200;
+		break;
+	case 0x90: case 0x92: case 0x94: case 0x96:
+	case 0x98: case 0x9a: case 0x9c: case 0x9e:
+		p = ~reg[0x70/2] >> 1 & 32;
+		for(i = a - 0x90; i < a - 0x90 + 2; i++){
+			wave[(wpos + 2 * i) & 31 + p] = v >> 4 & 0xf;
+			wave[(wpos + 2 * i + 1) & 31 + p] = v & 0xf;
+			v >>= 8;
+		}
+		break;
+	}
+}
+
+void
+audioinit(void)
+{
+	fd = open("/dev/audio", OWRITE);
+	if(fd < 0)
+		sysfatal("open: %r");
+	sbufp = sbuf;
+	sndwrite(SOUNDBIAS*2, 0x200);
+	evsamp.f = sampletick;
+	addevent(&evsamp, sratediv);
+}
+
+int
+audioout(void)
+{
+	int rc;
+	static int cl;
+	static vlong t0;
+
+	if(t0 == 0)
+		t0 = nsec();
+	if(sbufp == nil)
+		return -1;
+	if(sbufp == sbuf)
+		return 0;
+	cl = clock;
+	vlong t = nsec();
+	rc = warp10 ? (sbufp - sbuf) * 2 : write(fd, sbuf, (sbufp - sbuf) * 2);
+	vlong t2 = nsec();
+	double Δ = (t2 - t) / 1000000.0;
+	double τ = (t2 - t0) / 1000000.0;
+	double τ0 = rc * 1000.0 / (44100.0 * 4);
+	fprint(2, "%d %f %f\n", rc, Δ, τ-τ0);
+	t0 = t2;
+	if(rc > 0)
+		sbufp -= (rc+1)/2;
+	if(sbufp < sbuf)
+		sbufp = sbuf;
+	return 0;
+}
--- /dev/null
+++ b/sys/src/games/gba/ppu.c
@@ -1,0 +1,781 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "../eui.h"
+#include "dat.h"
+#include "fns.h"
+
+int hblank, ppuy;
+u8int bldy, blda, bldb;
+u32int hblclock;
+int ppux0;
+u32int pixcol[480];
+u8int pixpri[480];
+u8int pixwin[240];
+int objalpha;
+
+typedef struct bg bg;
+struct bg {
+	uchar n;
+	s32int rpx0, rpy0, rpx1, rpy1, rpx, rpy;
+	u16int tx, ty;
+	u8int tnx, tny;
+	
+	u8int mosaic, mctr, lasti;
+	u32int curc;
+	u8int curpri;
+};
+static bg bgst[4] = {{.n = 0}, {.n = 1}, {.n = 2}, {.n = 3}};
+
+Var ppuvars[] = {
+	VAR(hblank), VAR(ppuy), VAR(hblclock),
+	VAR(bldy), VAR(blda), VAR(bldb), VAR(objalpha),
+	VAR(bgst[2].rpx0), VAR(bgst[2].rpy0), VAR(bgst[3].rpx0), VAR(bgst[3].rpy0),
+	{nil, 0, 0},
+};
+
+typedef struct sprite sprite;
+struct sprite {
+	uchar w, wb, h;
+	s16int x;
+	uchar ysh;
+	
+	uchar *base;
+	u16int *pal;
+	u16int inc;
+
+	u32int t0;
+	u16int t1;
+	uchar depth;
+	
+	s32int rx, ry;
+	s16int dx, dy;
+	
+	u8int mctr, mcol;
+};
+static sprite sprt[128], *sp = sprt;
+enum {
+	SPRROT = 1<<8,
+	SPRDIS = 1<<9,
+	SPRDOUB = 1<<9,
+	SPRMOSA = 1<<12,
+	SPR8 = 1<<13,
+	SPRWIDE = 1<<14,
+	SPRTALL = 1<<15,
+	SPRHFLIP = 1<<28,
+	SPRVFLIP = 1<<29,
+	SPRSIZE0 = 1<<30,
+	SPRSIZE1 = 1<<31,
+
+	NOWIN = 0,
+	OBJWIN = 1,
+	WIN2 = 2,
+	WIN1 = 4,
+	
+	OBJALPHA = 1<<16,
+	SRCOBJ = 4<<17,
+	SRCBACK = 5<<17,
+	
+	VACANT = 0x10,
+	BACKDROP = 8,
+};
+#define SRCBG(n) ((n)<<17)
+
+void
+sprinit(void)
+{
+	u16int *p, *pp;
+	u16int cnt, t1;
+	u32int t0;
+	int budg;
+	uchar ws, h, hb, d, dy, s;
+	static uchar wss[16] = {3, 4, 5, 6, 4, 5, 5, 6, 3, 3, 4, 5};
+	static uchar hss[16] = {3, 4, 5, 6, 3, 3, 4, 5, 4, 5, 5, 6};
+
+	sp = sprt;
+	cnt = reg[DISPCNT];
+	budg = (cnt & HBLFREE) != 0 ? 954 : 1210;
+	for(p = oam; p < oam + 512; p += 4){
+		t0 = p[0];
+		if((t0 & (SPRROT|SPRDIS)) == SPRDIS)
+			continue;
+		t0 |= p[1] << 16;
+		s = t0 >> 30 & 3 | t0 >> 12 & 12;
+		hb = h = 1 << hss[s];
+		dy = ppuy - (u8int) t0;
+		if((t0 & (SPRROT|SPRDOUB)) == (SPRROT|SPRDOUB))
+			hb <<= 1;
+		if(dy >= hb || (u8int)t0 + hb > 256 && ppuy + 256 - (u8int)t0 >= hb)
+			continue;
+		if((t0 & SPRMOSA) != 0){
+			dy = dy - dy % ((reg[MOSAIC] >> 12 & 15) + 1);
+			sp->mctr = 0;
+		}
+		sp->x = (s32int)(t0 << 7) >> 23;
+		sp->t0 = t0;
+		ws = wss[s];
+		sp->wb = sp->w = 1<<ws;
+		sp->h = h;
+		sp->t1 = t1 = p[2];
+		sp->base = vram + 0x10000 + ((t1 & 0x3ff) << 5);
+		d = (t0 & SPR8) != 0;
+		sp->ysh = (cnt & OBJNOMAT) != 0 ? 2 + d + ws : 10;
+		if((t0 & SPRROT) != 0){
+			if((t0 & SPRDOUB) != 0)
+				sp->wb <<= 1;
+			budg -= 10 + sp->w*2;
+			pp = oam + 3 + (t0 >> 21 & 0x1f0);
+			sp->dx = pp[0];
+			sp->dy = pp[8];
+			sp->rx = (dy - hb/2) * (s16int) pp[4] + (sp->w << 7) - sp->dx * sp->wb/2;
+			sp->ry = (dy - hb/2) * (s16int)pp[12] + (sp->h << 7) - sp->dy * sp->wb/2;
+			if(sp->x < 0){
+				sp->rx -= sp->x * sp->dx;
+				sp->ry -= sp->x * sp->dy;
+			}
+		}else{
+			budg -= sp->w;
+			if((t0 & SPRVFLIP) != 0)
+				dy = h - 1 - dy;
+			sp->base += (dy & 7) << 2 + d;
+			sp->base += dy >> 3 << sp->ysh;
+			if((t0 & SPRHFLIP) != 0)
+				sp->base += sp->w - 7 << 2 + d;
+			sp->inc = (1 << 5 + d) - (1 << 2 + d);
+			if(sp->x < 0)
+				if((t0 & SPRHFLIP) != 0){
+					sp->base -= ((-sp->x & 7) >> 1 - d) + (-sp->x >> 3 << 5 + d);
+					if((t0 & SPR8) == 0 && (sp->x & 1) != 0)
+						sp->base--;
+				}else
+					sp->base += ((-sp->x & 7) >> 1 - d) + (-sp->x >> 3 << 5 + d);
+		}
+		if((t0 & SPR8) != 0)
+			sp->pal = pram + 0x100;
+		else
+			sp->pal = pram + 0x100 + (t1 >> 8 & 0xf0);
+		if(budg < 0)
+			break;
+		sp++;
+	}
+}
+
+void
+spr(int x1)
+{
+	int x0, i, dx, sx0, sx1;
+	u8int pri, v, d, *b;
+	u16int x, y;
+	u32int c, t0;
+	sprite *s;
+	
+	x0 = ppux0;
+	for(s = sprt; s < sp; s++){
+		if(s->x >= x1 || s->x + s->wb <= x0)
+			continue;
+		t0 = s->t0;
+		pri = s->t1 >> 10 & 3;
+		sx0 = s->x >= x0 ? s->x : x0;
+		sx1 = s->x + s->wb;
+		if(x1 < sx1)
+			sx1 = x1;
+		dx = sx0 - s->x;
+		for(i = sx0; i < sx1; i++, dx++){
+			if((t0 & SPRROT) != 0){
+				d = (t0 & SPR8) != 0;
+				x = s->rx >> 8;
+				y = s->ry >> 8;
+				s->rx += s->dx;
+				s->ry += s->dy;
+				if(x < s->w && y < s->h){
+					b = s->base;
+					b += (y & 7) << 2 + d;
+					b += y >> 3 << s->ysh;
+					b += (x & 7) >> 1 - d;
+					b += x >> 3 << 5 + d;
+					v = *b;
+					if(!d)
+						if((x & 1) != 0)
+							v >>= 4;
+						else
+							v &= 0xf;
+				}else
+					v = 0;
+			}else if((t0 & SPRHFLIP) != 0){
+				if((t0 & SPR8) != 0)
+					v = *--s->base;
+				else if((dx & 1) != 0)
+					v = *s->base & 0x0f;
+				else
+					v = *--s->base >> 4;
+				if((dx & 7) == 7)
+					s->base -= s->inc;
+			}else{
+				v = *s->base;
+				if((t0 & SPR8) != 0)
+					s->base++;
+				else if((dx & 1) != 0){
+					v >>= 4;
+					s->base++;
+				}else
+					v &= 0xf;
+				if((dx & 7) == 7)
+					s->base += s->inc;
+			}
+			if((t0 & SPRMOSA) != 0)
+				if(s->mctr == 0){
+					s->mctr = reg[MOSAIC] >> 8 & 15;
+					s->mcol = v;
+				}else{
+					--s->mctr;
+					v = s->mcol;
+				}
+			if(v != 0){
+				c = s->pal[v] | SRCOBJ;
+				switch(t0 >> 10 & 3){
+				case 1:
+					c |= OBJALPHA;
+					objalpha++;
+				case 0:
+					if(pri < pixpri[i]){
+						pixcol[i] = c;
+						pixpri[i] = pri;
+					}
+					break;
+				case 2:
+					if((reg[DISPCNT] & 1<<15) != 0)
+						pixwin[i] |= OBJWIN;
+					break;
+				}
+			}
+		}
+	}
+}
+
+void
+bgpixel(bg *b, int i, u32int c, int pri)
+{
+	u8int *p;
+	u32int *q;
+	int j;
+
+	if(b != nil){
+		c |= SRCBG(b->n);
+		if(b->mosaic){
+			for(j = (u8int)(b->lasti+1); j <= i; j++){
+				if(b->mctr == 0){
+					if(j == i){
+						b->curc = c;
+						b->curpri = pri;
+					}else
+						b->curpri = VACANT;
+					b->mctr = reg[MOSAIC] & 15;
+				}else
+					b->mctr--;
+				if(b->curpri != VACANT && (pixwin[j] & 1<<b->n) == 0)
+					bgpixel(nil, j, b->curc, b->curpri);
+			}
+			b->lasti = i;
+			return;
+		}
+	}
+	p = pixpri + i;
+	q = pixcol + i;
+	if(pri < p[0]){
+		p[240] = p[0];
+		p[0] = pri;
+		q[240] = q[0];
+		q[0] = c;
+	}else if(pri < p[240]){
+		p[240] = pri;
+		q[240] = c;
+	}
+}
+
+
+
+void
+bginit(bg *b, int scal, int)
+{
+	u16int x, y;
+	u16int *rr;
+	int msz;
+
+	b->mosaic = (reg[BG0CNT + b->n] & BGMOSAIC) != 0;
+	if(b->mosaic){
+		b->mctr = 0;
+		b->lasti = -1;
+	}
+	if(scal){
+		rr = reg + (b->n - 2 << 3);
+		if(ppuy == 0){
+			b->rpx0 = (s32int)(rr[BG2XL] | rr[BG2XH] << 16) << 4 >> 4;
+			b->rpy0 = (s32int)(rr[BG2YL] | rr[BG2YH] << 16) << 4 >> 4;
+		}
+		if(!b->mosaic || ppuy % ((reg[MOSAIC] >> 4 & 15) + 1) == 0){
+			b->rpx1 = b->rpx0;
+			b->rpy1 = b->rpy0;
+		}
+		b->rpx = b->rpx1;
+		b->rpy = b->rpy1;
+		b->rpx0 += (s16int)rr[BG2PB];
+		b->rpy0 += (s16int)rr[BG2PD];
+	}else{
+		rr = reg + (b->n << 1);
+		x = rr[BG0HOFS] & 0x1ff;
+		y = ppuy;
+		if(b->mosaic){
+			msz = (reg[MOSAIC] >> 4 & 15) + 1;
+			y = y - y % msz;
+		}
+		y += (rr[BG0VOFS] & 0x1ff);
+		b->tx = x >> 3;
+		b->ty = y >> 3;
+		b->tnx = x & 7;
+		b->tny = y & 7;
+	}
+}
+
+void
+bgsinit(void)
+{
+	switch(reg[DISPCNT] & 7){
+	case 0:
+		bginit(&bgst[0], 0, 0);
+		bginit(&bgst[1], 0, 0);
+		bginit(&bgst[2], 0, 0);
+		bginit(&bgst[3], 0, 0);
+		break;
+	case 1:
+		bginit(&bgst[0], 0, 0);
+		bginit(&bgst[1], 0, 0);
+		bginit(&bgst[2], 1, 0);
+		break;
+	case 2:
+		bginit(&bgst[2], 1, 0);
+		bginit(&bgst[3], 1, 0);
+		break;
+	case 3:
+	case 4:
+	case 5:
+		bginit(&bgst[2], 1, 1);
+		break;
+	}	
+}
+
+void
+bitbg(bg *b, int x1)
+{
+	u8int *base, *p, pri, d;
+	u16int cnt, *rr, sx, sy;
+	int i, v;
+	
+	cnt = reg[DISPCNT];
+	if((cnt & 1<<8 + b->n) == 0)
+		return;
+	rr = reg + (b->n - 2 << 3);
+	if((cnt & 7) != 5){
+		sx = 240 << 8;
+		sy = 160 << 8;
+		d = (cnt & 7) == 3;
+	}else{
+		sx = 160 << 8;
+		sy = 128 << 8;
+		d = 1;
+	}
+	base = vram;
+	if((cnt & FRAME) != 0 && (cnt & 7) != 3)
+		base += 0xa000;
+	pri = reg[BG0CNT + b->n] & 3;
+	for(i = ppux0; i < x1; i++){
+		if(((pixwin[i] & 1<<b->n) == 0 || b->mosaic) && (u32int)b->rpx < sx && (u32int)b->rpy < sy){
+			if(d){
+				p = base + 2 * (b->rpx >> 8) + 480 * (b->rpy >> 8);
+				v = p[0] | p[1] << 8;
+			}else{
+				v = base[(b->rpx >> 8) + 240 * (b->rpy >> 8)];
+				if(v != 0)
+					v = pram[v];
+				else
+					v = -1;
+			}
+			if(v >= 0)
+				bgpixel(b, i, v, pri);
+	
+		}
+		b->rpx += (s16int) rr[BG2PA];
+		b->rpy += (s16int) rr[BG2PC];
+	}
+}
+
+void
+txtbg(bg *b, int x1)
+{
+	u8int y, v, d, *cp;
+	u16int bgcnt, ta0, ta, tx, ty, t, *pal;
+	u32int ca;
+	int i, x, mx;
+	
+	if((reg[DISPCNT] & 1<<8 + b->n) == 0)
+		return;
+	bgcnt = reg[BG0CNT + b->n];
+	d = bgcnt >> 7 & 1;
+	tx = b->tx;
+	ty = b->ty;
+	ta0 = (bgcnt << 3 & 0xf800) + ((ty & 0x1f) << 6);
+	switch(bgcnt >> 14){
+	case 2: ta0 += ty << 6 & 0x800; break;
+	case 3: ta0 += ty << 7 & 0x1000; break;
+	}
+	x = ppux0;
+	i = b->tnx;
+	for(; x < x1; tx++, i = 0){
+		ta = ta0 + ((tx & 0x1f) << 1);
+		if((bgcnt & 1<<14) != 0)
+			ta += tx << 6 & 0x800;
+		t = vram[ta] | vram[ta+1] << 8;
+		if(d)
+			pal = pram;
+		else
+			pal = pram + (t >> 8 & 0xf0);
+		ca = (bgcnt << 12 & 0xc000) + ((t & 0x3ff) << 5+d);
+		if(ca >= 0x10000)
+			continue;
+		y = b->tny;
+		if((t & 1<<11) != 0)
+			y ^= 7;
+		ca += y << 2+d;
+		cp = vram + ca;
+		for(; i < 8; i++, x++){
+			if(x >= x1)
+				goto out;
+			if((pixwin[x] & 1<<b->n) != 0 && !b->mosaic)
+				continue;
+			mx = i;
+			if((t & 1<<10) != 0)
+				mx ^= 7;
+			v = cp[mx >> 1-d];
+			if(!d)
+				if((mx & 1) != 0)
+					v >>= 4;
+				else
+					v &= 0xf;
+			if(v != 0)
+				bgpixel(b, x, pal[v], bgcnt & 3);
+		}
+	}
+out:
+	b->tx = tx;
+	b->tnx = i;
+}
+
+void
+rotbg(bg *b, int x1)
+{
+	uchar *p, v;
+	u16int bgcnt, *rr, ta;
+	int i, row, sz, x, y;
+
+	rr = reg + (b->n - 2 << 3);
+	if((reg[DISPCNT] & 1<<8 + b->n) == 0)
+		return;
+	bgcnt = reg[BG0CNT + b->n];
+	row = (bgcnt >> 14) + 4;
+	sz = 1 << 3 + row;
+	for(i = ppux0; i < x1; i++){
+		x = b->rpx >> 8;
+		y = b->rpy >> 8;
+		b->rpx += (s16int) rr[BG2PA];
+		b->rpy += (s16int) rr[BG2PC];
+		if((pixwin[i] & 1<<b->n) != 0 && !b->mosaic)
+			continue;
+		if((bgcnt & DISPWRAP) != 0){
+			x &= sz - 1;
+			y &= sz - 1;
+		}else if((uint)x >= sz || (uint)y >= sz)
+			 continue;
+		ta = (bgcnt << 3 & 0xf800) + ((y >> 3) << row) + (x >> 3);
+		p = vram + (bgcnt << 12 & 0xc000) + (vram[ta] << 6);
+		p += (x & 7) + ((y & 7) << 3);
+		if((v = *p) != 0)
+			bgpixel(b, i, pram[v], bgcnt & 3);
+		
+	}
+}
+
+void
+windows(int x1)
+{
+	static u8int wintab[8] = {2, 3, 1, 1, 0, 0, 0, 0};
+	int i, sx0, sx1;
+	u16int v, h;
+	u16int cnt;
+	
+	cnt = reg[DISPCNT];
+	if((cnt >> 13) != 0){
+		if((cnt & 1<<13) != 0){
+			v = reg[WIN0V];
+			h = reg[WIN0H];
+			if(ppuy < (u8int)v && ppuy >= v >> 8){
+				sx1 = (u8int)h;
+				sx0 = h >> 8;
+				if(sx0 < ppux0)
+					sx0 = ppux0;
+				if(sx1 > x1)
+					sx1 = x1;
+				for(i = sx0; i < sx1; i++)
+					pixwin[i] |= WIN1;
+			}
+		}
+		if((cnt & 1<<14) != 0){
+			v = reg[WIN1V];
+			h = reg[WIN1H];
+			if(ppuy < (u8int)v && ppuy >= v >> 8){
+				sx1 = (u8int)h;
+				sx0 = h >> 8;
+				if(sx0 < ppux0)
+					sx0 = ppux0;
+				if(sx1 > x1)
+					sx1 = x1;
+				for(i = sx0; i < sx1; i++)
+					pixwin[i] |= WIN2;
+			}
+		}
+		for(i = ppux0; i < x1; i++){
+			v = wintab[pixwin[i]];
+			h = reg[WININ + (v & 2) / 2];
+			if((v & 1) != 0)
+				h >>= 8;
+			pixwin[i] = ~h;
+		}
+	}
+	for(i = ppux0; i < x1; i++)
+		if(pixpri[i] == VACANT || (pixwin[i] & 1<<4) != 0){
+			pixcol[i] = pram[0] | SRCBACK;
+			pixpri[i] = BACKDROP;
+		}else{
+			pixcol[i+240] = pram[0] | SRCBACK;
+			pixpri[i+240] = BACKDROP;
+		}
+	objalpha = 0;
+}
+
+u16int
+mix(u16int c1, u16int c2)
+{
+	u16int r, g, b, eva, evb;
+
+	eva = blda;
+	evb = bldb;
+	b = ((c1 & 0x7c00) * eva + (c2 & 0x7c00) * evb) >> 4;
+	g = ((c1 & 0x03e0) * eva + (c2 & 0x03e0) * evb) >> 4;
+	r = ((c1 & 0x001f) * eva + (c2 & 0x001f) * evb) >> 4;
+	if(b > 0x7c00) b = 0x7c00;
+	if(g > 0x03e0) g = 0x03e0;
+	if(r > 0x001f) r = 0x001f;
+	return b & 0x7c00 | g & 0x03e0 | r;
+}
+
+u16int
+brighten(u16int c1)
+{
+	u16int r, g, b, y;
+	
+	y = bldy;
+	b = c1 & 0x7c00;
+	b = b + (0x7c00 - b) * y / 16;
+	g = c1 & 0x03e0;
+	g = g + (0x03e0 - g) * y / 16;
+	r = c1 & 0x001f;
+	r = r + (0x001f - r) * y / 16;
+	if(b > 0x7c00) b = 0x7c00;
+	if(g > 0x03e0) g = 0x03e0;
+	if(r > 0x001f) r = 0x001f;
+	return b & 0x7c00 | g & 0x03e0 | r;
+}
+
+u16int
+darken(u16int c1)
+{
+	u16int r, g, b, y;
+
+	y = 16 - bldy;
+	b = c1 & 0x7c00;
+	b = b * y / 16;
+	g = c1 & 0x03e0;
+	g = g * y / 16;
+	r = c1 & 0x001f;
+	r = r * y / 16;
+	return b & 0x7c00 | g & 0x03e0 | r;
+}
+
+void
+colormath(int x1)
+{
+	u16int bldcnt;
+	u32int *p;
+	int i;
+	
+	bldcnt = reg[BLDCNT];
+	if((bldcnt & 3<<6) == 0 && objalpha == 0)
+		return;
+	p = pixcol + ppux0;
+	for(i = ppux0; i < x1; i++, p++){
+		if((*p & OBJALPHA) != 0)
+			goto alpha;
+		if((pixwin[i] & 1<<5) != 0 || (bldcnt & 1<<(*p >> 17)) == 0)
+			continue;
+		switch(bldcnt >> 6 & 3){
+		case 1:
+		alpha:
+			if((bldcnt & 1<<8+(p[240] >> 17)) == 0)
+				continue;
+			*p = mix(*p, p[240]);
+			break;
+		case 2:
+			*p = brighten(*p);
+			break;
+		case 3:
+			*p = darken(*p);
+			break;
+		}
+	}
+}
+
+void
+syncppu(int x1)
+{
+	int i;
+	u16int cnt;
+
+	if(hblank || ppuy >= 160)
+		return;
+	if(x1 >= 240)
+		x1 = 240;
+	else if(x1 <= ppux0)
+		return;
+	cnt = reg[DISPCNT];
+	if((cnt & FBLANK) != 0){
+		for(i = ppux0; i < x1; i++)
+			pixcol[i] = 0xffff;
+		ppux0 = x1;
+		return;
+	}
+
+	if((cnt & 1<<12) != 0)
+		spr(x1);
+	windows(x1);
+	switch(cnt & 7){
+	case 0:
+		txtbg(&bgst[0], x1);
+		txtbg(&bgst[1], x1);
+		txtbg(&bgst[2], x1);
+		txtbg(&bgst[3], x1);
+		break;
+	case 1:
+		txtbg(&bgst[0], x1);
+		txtbg(&bgst[1], x1);
+		rotbg(&bgst[2], x1);
+		break;
+	case 2:
+		rotbg(&bgst[2], x1);
+		rotbg(&bgst[3], x1);
+		break;
+	case 3:
+	case 4:
+	case 5:
+		bitbg(&bgst[2], x1);
+	}
+	colormath(x1);
+	ppux0 = x1;
+}
+
+void
+linecopy(u32int *p, int y)
+{
+	uchar *q;
+	u16int *r;
+	u16int v;
+	union { u16int w; u8int b[2]; } u;
+	int n;
+
+	q = pic + y * 240 * 2;
+	r = (u16int*)q;
+	n = 240;
+	while(n--){
+		v = *p++;
+		u.b[0] = v;
+		u.b[1] = v >> 8;
+		*r++ = u.w;
+	}
+}
+
+void
+hblanktick(void *)
+{
+	extern Event evhblank;
+	u16int stat;
+
+	stat = reg[DISPSTAT];
+	if(hblank){
+		hblclock = clock + evhblank.time;
+		addevent(&evhblank, 240*4);
+		hblank = 0;
+		ppux0 = 0;
+		memset(pixpri, VACANT, sizeof(pixpri));
+		memset(pixwin, 0, 240);
+		if(++ppuy >= 228){
+			ppuy = 0;
+			flush();
+		}
+		if(ppuy < 160){
+			sprinit();
+			bgsinit();
+		}else if(ppuy == 160){
+			dmastart(DMAVBL);
+			if((stat & IRQVBLEN) != 0)
+				setif(IRQVBL);
+		}
+		if((stat & IRQVCTREN) != 0 && ppuy == stat >> 8)
+			setif(IRQVCTR);
+	}else{
+		syncppu(240);
+		if(ppuy < 160)
+			linecopy(pixcol, ppuy);
+		addevent(&evhblank, 68*4);
+		hblank = 1;
+		if((stat & IRQHBLEN) != 0)
+			setif(IRQHBL);
+		if(ppuy < 160)
+			dmastart(DMAHBL);
+	}
+}
+
+void
+ppuwrite(u16int a, u16int v)
+{
+	syncppu((clock - hblclock) / 4);
+	switch(a){
+	case BLDALPHA*2:
+		blda = v & 0x1f;
+		if(blda > 16)
+			blda = 16;
+		bldb = v >> 8 & 0x1f;
+		if(bldb > 16)
+			bldb = 16;
+		break;
+	case BLDY*2:
+		bldy = v & 0x1f;
+		if(bldy > 16)
+			bldy = 16;
+		break;
+	case BG2XL*2: bgst[2].rpx0 = bgst[2].rpx0 & 0xffff0000 | v; break;
+	case BG2XH*2: bgst[2].rpx0 = bgst[2].rpx0 & 0xffff | (s32int)(v << 20) >> 4; break;
+	case BG2YL*2: bgst[2].rpy0 = bgst[2].rpy0 & 0xffff0000 | v; break;
+	case BG2YH*2: bgst[2].rpy0 = bgst[2].rpy0 & 0xffff | (s32int)(v << 20) >> 4; break;
+	case BG3XL*2: bgst[3].rpx0 = bgst[3].rpx0 & 0xffff0000 | v; break;
+	case BG3XH*2: bgst[3].rpx0 = bgst[3].rpx0 & 0xffff | (s32int)(v << 20) >> 4; break;
+	case BG3YL*2: bgst[3].rpy0 = bgst[3].rpy0 & 0xffff0000 | v; break;
+	case BG3YH*2: bgst[3].rpy0 = bgst[3].rpy0 & 0xffff | (s32int)(v << 20) >> 4; break;
+	}
+}
--- /dev/null
+++ b/sys/src/games/md/vdp.c
@@ -1,0 +1,378 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "../eui.h"
+#include "dat.h"
+#include "fns.h"
+
+u16int vdpstat = 0x3400;
+int vdpx, vdpy, vdpyy, frame, intla;
+u16int hctr;
+static int xmax, xdisp;
+static int sx, snx, col, pri, lum;
+enum { DARK, NORM, BRIGHT };
+enum { ymax = 262, yvbl = 234 };
+
+void
+vdpmode(void)
+{
+	if((reg[MODE4] & WIDE) != 0){
+		xmax = 406;
+		xdisp = 320;
+	}else{
+		xdisp = 256;
+		xmax = 342;
+	}
+	intla = (reg[MODE4] & 6) == 6;
+}
+
+static void
+pixeldraw(int x, int y, int v)
+{
+	u32int *p;
+	union { u32int l; u8int b[4]; } u;
+
+	u.b[0] = v >> 16;
+	u.b[1] = v >> 8;
+	u.b[2] = v;
+	u.b[3] = 0;
+	*((u32int *)pic + (x + y * 320)) = u.l;
+}
+
+static u32int
+shade(u32int v, int l)
+{
+	if(l == 1)
+		return v;
+	if(l == 2)
+		return v << 1 & 0xefefef;
+	return v >> 1 & 0xf7f7f7;
+}
+
+static void
+pixel(int v, int p)
+{
+	if(p >= pri){
+		col = v;
+		pri = p;
+	}
+}
+
+struct pctxt {
+	u8int ws, w, hs, h;
+	u16int tx, ty;
+	u8int tnx, tny;
+	u16int t;
+	u32int c;
+} pctxt[3];
+int lwin, rwin;
+
+static void
+tile(struct pctxt *p)
+{
+	u16int a;
+	int y;
+	
+	switch(p - pctxt){
+	default: a = (reg[PANT] & 0x38) << 9; break;
+	case 1: a = (reg[PBNT] & 7) << 12; break;
+	case 2: a = (reg[PWNT] & 0x3e) << 9; break;
+	}
+	a += p->ty << p->ws;
+	a += p->tx;
+	p->t = vram[a];
+	y = p->tny;
+	if(intla){
+		if((p->t & 0x1000) != 0)
+			y = 15 - y;
+		a = (p->t & 0x7ff) << 5 | y << 1;
+	}else{
+		if((p->t & 0x1000) != 0)
+			y = 7 - y;
+		a = (p->t & 0x7ff) << 4 | y << 1;
+	}
+	p->c = vram[a] << 16 | vram[a+1];
+}
+
+static void
+planeinit(void)
+{
+	static int szs[] = {5, 6, 6, 7};
+	int v, a, i;
+	struct pctxt *p;
+	
+	pctxt[0].hs = pctxt[1].hs = szs[reg[PLSIZ] >> 4 & 3];
+	pctxt[0].ws = pctxt[1].ws = szs[reg[PLSIZ] & 3];
+	pctxt[2].ws = (reg[MODE4] & WIDE) != 0 ? 6 : 5;
+	pctxt[2].hs = 5;
+	for(i = 0; i <= 2; i++){
+		pctxt[i].h = 1<<pctxt[i].hs;
+		pctxt[i].w = 1<<pctxt[i].ws;
+	}
+	a = reg[HORSCR] << 9 & 0x7fff;
+	switch(reg[MODE3] & 3){
+	case 1: a += vdpy << 1 & 0xe; break;
+	case 2: a += vdpy << 1 & 0xff0; break;
+	case 3: a += vdpy << 1 & 0xffe; break;
+	}
+	for(i = 0; i < 2; i++){
+		p = pctxt + i;
+		v = -(vram[a + i] & 0x3ff);
+		p->tnx = v & 7;
+		p->tx = v >> 3 & pctxt[i].w - 1;
+		if(intla){
+			v = vsram[i] + vdpyy;
+			p->tny = v & 15;
+			p->ty = v >> 4 & pctxt[i].h - 1;
+		}else{
+			v = vsram[i] + vdpy;
+			p->tny = v & 7;
+			p->ty = v >> 3 & pctxt[i].h - 1;
+		}
+		tile(p);
+		if(p->tnx != 0)
+			if((p->t & 0x800) != 0)
+				p->c >>= p->tnx << 2;
+			else
+				p->c <<= p->tnx << 2;
+	}
+	sx = 0;
+	snx = 0;
+	v = reg[WINV] << 3 & 0xf8;
+	if((reg[WINV] & 0x80) != 0 ? vdpy < v : vdpy >= v){
+		lwin = 0;
+		rwin = reg[WINH] << 4 & 0x1f0;
+		if((reg[WINH] & 0x80) != 0){
+			lwin = rwin;
+			rwin = 320;
+		}
+	}else{
+		lwin = 0;
+		rwin = 320;
+	}
+	if(rwin > lwin){
+		p = pctxt + 2;
+		p->tx = lwin >> 3 & pctxt[2].w - 1;
+		p->tnx = lwin & 7;
+		p->tny = vdpy & 7;
+		p->ty = vdpy >> 3 & pctxt[2].h - 1;
+		tile(p);
+	}
+}
+
+static void
+plane(int n, int vis)
+{
+	struct pctxt *p;
+	u8int v, pr;
+	
+	p = pctxt + n;
+	if((p->t & 0x800) != 0){
+		v = p->c & 15;
+		p->c >>= 4;
+	}else{
+		v = p->c >> 28;
+		p->c <<= 4;
+	}
+	if(vis != 0){
+		if(v != 0){
+			v |= p->t >> 9 & 48;
+			pr = 2 - (n & 1) + (p->t >> 13 & 4);
+			pixel(v, pr);
+		}
+		lum |= p->t >> 15;
+	}
+	if(++p->tnx == 8){
+		p->tnx = 0;
+		if(++p->tx == p->w)
+			p->tx = 0;
+		tile(pctxt + n);
+	}
+}
+
+static void
+planes(void)
+{
+	int i, w;
+	u16int v;
+
+	if((reg[MODE3] & 4) != 0 && ++snx == 16){
+		snx = 0;
+		sx++;
+		for(i = 0; i < 2; i++){
+			v = vsram[sx + i] + vdpy;
+			pctxt[i].tny = v & 7;
+			pctxt[i].ty = v >> 3 & pctxt[i].h - 1;
+		}
+	}
+	w = vdpx < rwin && vdpx >= lwin;
+	plane(0, !w);
+	plane(1, 1);
+	if(w)
+		plane(2, 1);
+}
+
+static struct sprite {
+	u16int y, x;
+	u8int w, h;
+	u16int t;
+	u32int c[4];
+} spr[21], *lsp;
+
+static void
+spritesinit(void)
+{
+	u16int t, *p, dy, c;
+	u32int v;
+	int i, ns, np, nt;
+	struct sprite *q;
+	
+	t = (reg[SPRTAB] << 8 & 0x7f00);
+	p = vram + t;
+	q = spr;
+	ns = (reg[MODE4] & WIDE) != 0 ? 20 : 16;
+	np = 0;
+	nt = 0;
+	do{
+		if(intla){
+			q->y = (p[0] & 0x3ff) - 256;
+			q->h = (p[1] >> 8 & 3) + 1 << 4;
+			dy = vdpyy - q->y;
+		}else{
+			q->y = (p[0] & 0x3ff) - 128;
+			q->h = (p[1] >> 8 & 3) + 1 << 3;
+			dy = vdpy - q->y;
+		}
+		if(dy >= q->h)
+			continue;
+		q->t = p[2];
+		if((q->t & 0x1000) != 0)
+			dy = q->h + ~dy;
+		q->x = (p[3] & 0x3ff) - 128;
+		if(q->x == 0xff80)
+			break;
+		q->w = (p[1] >> 10 & 3) + 1 << 3;
+		c = ((q->t & 0x7ff) << 4+intla) + (dy << 1);
+		for(i = 0; i < q->w >> 3 && np < xdisp; i++){
+			v = vram[c] << 16 | vram[(u16int)(c+1)];
+			c += q->h << 1;
+			if((q->t & 0x800) != 0)
+				q->c[(q->w >> 3) - 1 - i] = v;
+			else
+				q->c[i] = v;
+			np += 8;
+		}
+		if((u16int)-q->x < q->w){
+			i = -(s16int)q->x;
+			if((q->t & 0x800) != 0)
+				q->c[i>>3] >>= (i & 7) << 2;
+			else
+				q->c[i>>3] <<= (i & 7) << 2;
+		}
+		if(++q == spr + ns || np >= xdisp){
+			vdpstat |= STATOVR;
+			break;
+		}
+	}while(p = vram + (u16int)(t + ((p[1] & 0x7f) << 2)), p - vram != t && ++nt < 80);
+	lsp = q;
+}
+
+static void
+sprites(void)
+{
+	struct sprite *p;
+	u16int dx;
+	int v, col, set;
+	u32int *c;
+	
+	set = 0;
+	col = 0;
+	for(p = spr; p < lsp; p++){
+		dx = vdpx - p->x;
+		if(dx >= p->w)
+			continue;
+		c = p->c + (dx >> 3);
+		if((p->t & 0x800) != 0){
+			v = *c & 15;
+			*c >>= 4;
+		}else{
+			v = *c >> 28;
+			*c <<= 4;
+		}
+		if(v != 0)
+			if(set != 0)
+				vdpstat |= STATCOLL;
+			else{
+				set = 1 | p->t & 0x8000;
+				col = p->t >> 9 & 48 | v;
+			}
+	}
+	if(set)
+		if((reg[MODE4] & SHI) != 0)
+			if((col & 0xfe) == 0x3e)
+				lum = col & 1;
+			else{
+				pixel(col, set >> 13 | 2);
+				if((col & 0xf) == 0xe)
+					lum = 1;
+				else
+					lum |= set >> 15;
+			}
+		else
+			pixel(col, set >> 13 | 2);
+}
+
+void
+vdpstep(void)
+{
+	u32int v;
+
+	if(vdpx == 0){
+		planeinit();
+		spritesinit();
+	}
+	if(vdpx < 320 && vdpy < 224)
+		if(vdpx < xdisp){
+			col = reg[BGCOL] & 0x3f;
+			pri = 0;
+			lum = 0;
+			planes();
+			sprites();
+			if((reg[MODE2] & 0x40) != 0 && (vdpx >= 8 || (reg[MODE1] & 0x20) == 0)){
+				v = cramc[col];
+				if((reg[MODE4] & SHI) != 0)
+					v = shade(v, lum);
+				pixeldraw(vdpx, vdpy, v);
+			}else
+				pixeldraw(vdpx, vdpy, 0);
+		}else
+			pixeldraw(vdpx, vdpy, 0xcccccc);
+	if(++vdpx >= xmax){
+		z80irq = 0;
+		vdpx = 0;
+		if(++vdpy >= ymax){
+			vdpy = 0;
+			irq &= ~INTVBL;
+			vdpstat ^= STATFR;
+			vdpstat &= ~(STATINT | STATVBL | STATOVR | STATCOLL);
+			flush();
+		}
+		if(intla)
+			vdpyy = vdpy << 1 | frame;
+		if(vdpy == 0 || vdpy >= 225)
+			hctr = reg[HORCTR];
+		else
+			if(hctr-- == 0){
+				if((reg[MODE1] & IE1) != 0)
+					irq |= INTHOR;
+				hctr = reg[HORCTR];
+			}
+		if(vdpy == yvbl){
+			vdpstat |= STATVBL | STATINT;
+			frame ^= 1;
+			z80irq = 1;
+			if((reg[MODE2] & IE0) != 0)
+				irq |= INTVBL;
+		}
+	}
+}
--- /dev/null
+++ b/sys/src/games/nes/ppu.c
@@ -1,0 +1,280 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include "../eui.h"
+#include "dat.h"
+#include "fns.h"
+
+int ppuy, ppux, odd;
+
+static void
+pixel(int x, int y, int val, int back)
+{
+	union { u8int c[4]; u32int l; } u;
+	static u8int palred[64] = {
+		0x7C, 0x00, 0x00, 0x44, 0x94, 0xA8, 0xA8, 0x88, 
+		0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+		0xBC, 0x00, 0x00, 0x68, 0xD8, 0xE4, 0xF8, 0xE4, 
+		0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+		0xF8, 0x3C, 0x68, 0x98, 0xF8, 0xF8, 0xF8, 0xFC, 
+		0xF8, 0xB8, 0x58, 0x58, 0x00, 0x78, 0x00, 0x00, 
+		0xFC, 0xA4, 0xB8, 0xD8, 0xF8, 0xF8, 0xF0, 0xFC, 
+		0xF8, 0xD8, 0xB8, 0xB8, 0x00, 0xF8, 0x00, 0x00, 
+	};
+	static u8int palgreen[64] = {
+		0x7C, 0x00, 0x00, 0x28, 0x00, 0x00, 0x10, 0x14, 
+                0x30, 0x78, 0x68, 0x58, 0x40, 0x00, 0x00, 0x00, 
+                0xBC, 0x78, 0x58, 0x44, 0x00, 0x00, 0x38, 0x5C, 
+                0x7C, 0xB8, 0xA8, 0xA8, 0x88, 0x00, 0x00, 0x00, 
+                0xF8, 0xBC, 0x88, 0x78, 0x78, 0x58, 0x78, 0xA0, 
+                0xB8, 0xF8, 0xD8, 0xF8, 0xE8, 0x78, 0x00, 0x00, 
+                0xFC, 0xE4, 0xB8, 0xB8, 0xB8, 0xA4, 0xD0, 0xE0, 
+                0xD8, 0xF8, 0xF8, 0xF8, 0xFC, 0xD8, 0x00, 0x00, 
+	};
+	static u8int palblue[64] = {
+		0x7C, 0xFC, 0xBC, 0xBC, 0x84, 0x20, 0x00, 0x00, 
+                0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 
+                0xBC, 0xF8, 0xF8, 0xFC, 0xCC, 0x58, 0x00, 0x10, 
+                0x00, 0x00, 0x00, 0x44, 0x88, 0x00, 0x00, 0x00, 
+                0xF8, 0xFC, 0xFC, 0xF8, 0xF8, 0x98, 0x58, 0x44, 
+                0x00, 0x18, 0x54, 0x98, 0xD8, 0x78, 0x00, 0x00, 
+                0xFC, 0xFC, 0xF8, 0xF8, 0xF8, 0xC0, 0xB0, 0xA8, 
+                0x78, 0x78, 0xB8, 0xD8, 0xFC, 0xF8, 0x00, 0x00, 
+	};
+
+	u.c[0] = palblue[val];
+	u.c[1] = palgreen[val];
+	u.c[2] = palred[val];
+	u.c[3] = back ? 0 : 0xFF;
+	*((u32int *)pic + y * 256 + x) = u.l;
+}
+
+static int
+iscolor(int x, int y)
+{
+	return pic[4 * (y * 256 + x) + 3] != 0;
+}
+
+static int
+pal(int c, int a, int spr)
+{
+	if(c == 0)
+		return ppuread(0x3F00);
+	return ppuread(0x3F00 | ((a&3)<<2) | (c & 3) | (spr << 4));
+}
+
+static void
+incppuy(void)
+{
+	int y;
+
+	if((ppuv & 0x7000) != 0x7000){
+		ppuv += 0x1000;
+		return;
+	}
+	y = (ppuv >> 5) & 31;
+	if(y++ == 29){
+		y = 0;
+		ppuv ^= 0x800;
+	}
+	ppuv = (ppuv & 0x0C1F) | ((y & 31) << 5);
+}
+
+static void
+drawbg(void)
+{
+	static int t;
+	u8int c, a;
+	static u8int nr1, nr2, na;
+	static u16int r1, r2, a1, a2;
+	
+	if(ppux >= 2 && ppux <= 257 || ppux >= 322 && ppux <= 337){
+		c = (r1 >> (15-ppusx)) & 1 | (r2 >> (14-ppusx)) & 2;
+		if(ppuy < 240 && ppux <= 257){
+			a = (a1 >> (15-ppusx)) & 1 | (a2 >> (14-ppusx)) & 2;
+			pixel(ppux-2, ppuy, pal(c, a, 0), c == 0);
+		}
+		r1 <<= 1;
+		r2 <<= 1;
+		a1 <<= 1;
+		a2 <<= 1;
+	}
+	if(ppux >= 256 && ppux <= 320){
+		if(ppux == 256)
+			incppuy();
+		if(ppux == 257)
+			ppuv = (ppuv & 0x7BE0) | (pput & 0x041F);
+		return;
+	}
+	switch(ppux & 7){
+	case 0:
+		if(ppux != 0){
+			if((ppuv & 0x1f) == 0x1f){
+				ppuv &= ~0x1f;
+				ppuv ^= 0x400;
+			}else
+				ppuv++;
+		}
+		break;
+	case 1:
+		t = ppuread(0x2000 | ppuv & 0x0FFF);
+		if(ppux != 1){
+			r1 |= nr1;
+			r2 |= nr2;
+			if(na & 1)
+				a1 |= 0xff;
+			if(na & 2)
+				a2 |= 0xff;
+		}
+		break;
+	case 3:
+		na = ppuread(0x23C0 | ppuv & 0x0C00 | ((ppuv & 0x0380) >> 4) | ((ppuv & 0x001C) >> 2));
+		if((ppuv & 0x0002) != 0) na >>= 2;
+		if((ppuv & 0x0040) != 0) na >>= 4;
+		break;
+	case 5:
+		nr1 = ppuread(((mem[PPUCTRL] & BGTABLE) << 8) | t << 4 | ppuv >> 12);
+		break;
+	case 7:
+		nr2 = ppuread(((mem[PPUCTRL] & BGTABLE) << 8) | t << 4 | ppuv >> 12 | 8);
+		break;
+	}
+}
+
+static void
+drawsprites(int show)
+{
+	uchar *p;
+	int big, dx, dy, i, x, cc, pri;
+	u8int r1, r2, c;
+	static int n, m, nz, s0, t0;
+	static struct { u8int x, a; u16int t; } s[8], *sp;
+	static struct { u8int x, a, r1, r2; } t[8];
+
+	big = (mem[PPUCTRL] & BIGSPRITE) != 0;
+	if(ppux == 65){
+		s0 = 0;
+		for(p = oam, sp = s, n = 0; p < oam + sizeof(oam); p += 4){
+			if((dy = p[0]) >= 0xEF)
+				continue;
+			dy = ppuy - dy;
+			if(dy < 0 || dy >= (big ? 16 : 8))
+				continue;
+			if(p == oam)
+				s0 = 1;
+			sp->t = p[1];
+			sp->a = p[2];
+			sp->x = p[3];
+			if((sp->a & (1<<7)) != 0)
+				dy = (big ? 15 : 7) - dy;
+			if(big){
+				sp->t |= (sp->t & 1) << 8;
+				if(dy >= 8){
+					sp->t |= 1;
+					dy -= 8;
+				}else
+					sp->t &= 0x1fe;
+			}else
+				sp->t |= (mem[PPUCTRL] & SPRTABLE) << 5;
+			sp->t = sp->t << 4 | dy;
+			sp++;
+			if(++n == 8)
+				break;
+		}
+	}
+	if(ppux >= 2 && ppux <= 257 && m > 0){
+		x = ppux - 2;
+		dx = x - t[0].x;
+		if(t0 && dx >= 0 && dx < 8 && ppux != 257){
+			if((nz & 1) != 0 && iscolor(x, ppuy) && show)
+				mem[PPUSTATUS] |= SPRITE0HIT;
+			nz >>= 1;
+		}
+		cc = -1;
+		pri = 0;
+		for(i = m - 1; i >= 0; i--){
+			dx = x - t[i].x;
+			if(dx < 0 || dx > 7)
+				continue;
+			c = (t[i].r1 & 1) | (t[i].r2 & 1) << 1;
+			if(c != 0){
+				cc = pal(c, t[i].a & 3, 1);
+				pri = (t[i].a & (1<<5)) == 0;
+			}
+			t[i].r1 >>= 1;
+			t[i].r2 >>= 1;
+		}
+		if(cc != -1 && show && (pri || !iscolor(x, ppuy)))
+			pixel(x, ppuy, cc, 0);
+	}
+	if(ppux == 257){
+		for(i = 0; i < n; i++){
+			r1 = ppuread(s[i].t);
+			r2 = ppuread(s[i].t | 8);
+			if((s[i].a & (1<<6)) == 0){
+				r1 = ((r1 * 0x80200802ULL) & 0x0884422110ULL) * 0x0101010101ULL >> 32;
+				r2 = ((r2 * 0x80200802ULL) & 0x0884422110ULL) * 0x0101010101ULL >> 32;
+			}
+			t[i].x = s[i].x;
+			t[i].a = s[i].a;
+			t[i].r1 = r1;
+			t[i].r2 = r2;
+		}
+		m = n;
+		nz = t[0].r1 | t[0].r2;
+		t0 = s0;
+	}
+}
+
+static void
+flush(void)
+{
+	flushmouse(1);
+	flushscreen();
+	flushaudio(audioout);
+}
+
+void
+ppustep(void)
+{
+	extern int nmi;
+	int mask;
+
+	if(ppuy < 240 || ppuy == 261){
+		mask = mem[PPUMASK];
+		if((mask & BGDISP) != 0)
+			drawbg();
+		if((((mask & BGDISP) == 0 && ppux <= 257 || ppux < 10 && (mask & BG8DISP) == 0) && ppux >= 2) && ppuy != 261)
+			pixel(ppux - 2, ppuy, ppuread(0x3F00), 1);
+		if((mask & SPRITEDISP) != 0 && ppuy != 261)
+			drawsprites(ppux >= 10 || (mask & SPRITE8DISP) != 0);
+		if(ppux == 240 && (mask & SPRITEDISP) != 0)
+			mapper[map](SCAN, 0);
+		if(ppuy == 261){
+			if(ppux == 1)
+				mem[PPUSTATUS] &= ~(PPUVBLANK|SPRITE0HIT);
+			else if(ppux >= 280 && ppux <= 304 && (mask & BGDISP) != 0)
+				ppuv = (pput & 0x7BE0) | (ppuv & 0x041F);
+		}
+	}else if(ppuy == 241){
+		if(ppux == 1){
+			mem[PPUSTATUS] |= PPUVBLANK;
+			if((mem[PPUCTRL] & PPUNMI) != 0)
+				nmi = 2;
+			flush();
+		}
+	}
+	ppux++;
+	if(ppux > 340){
+		ppux = 0;
+		ppuy++;
+		if(ppuy > 261){
+			ppuy = 0;
+			if(odd && (mem[PPUMASK] & (BGDISP | SPRITEDISP)) != 0)
+				ppux++;
+			odd ^= 1;
+		}
+	}
+}
--- /dev/null
+++ b/sys/src/games/snes/ppu.c
@@ -1,0 +1,936 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "../eui.h"
+#include "dat.h"
+#include "fns.h"
+
+int ppux, ppuy, rx;
+static u8int mode, bright, pixelpri[2], hires;
+static u32int pixelcol[2];
+u16int vtime = 0x1ff, htime = 0x1ff, subcolor;
+u16int hofs[5], vofs[5];
+s16int m7[6];
+
+enum {
+	M7A,
+	M7B,
+	M7C,
+	M7D,
+	M7X,
+	M7Y
+};
+
+enum { OBJ = 4, COL = 5, OBJNC = 8 };
+
+static u16int
+darken(u16int v)
+{
+	u8int r, g, b;
+	
+	r = (v >> 10) & 0x1f;
+	g = (v >> 5) & 0x1f;
+	b = v & 0x1f;
+	r = r * bright / 15;
+	g = g * bright / 15;
+	b = b * bright / 15;
+	return r << 10 | g << 5 | b;
+}
+
+static void
+pixeldraw(int x, int y, u16int v, int s)
+{
+	union { u16int w; u8int b[2]; } u;
+
+	if(bright != 0xf && s >= 0)
+		v = darken(v);
+	u.b[0] = v;
+	u.b[1] = v >> 8;
+	*((u16int *)pic + (x + y * 256)) = u.w;	/* FIXME: some weird shit in the diff */
+}
+
+static int
+window(int n)
+{
+	int a, w1, w2;
+
+	a = reg[0x2123 + (n >> 1)];
+	if((n & 1) != 0)
+		a >>= 4;
+	if((a & (WIN1|WIN2)) == 0)
+		return 0;
+	w1 = rx >= reg[0x2126] && rx <= reg[0x2127];
+	w2 = rx >= reg[0x2128] && rx <= reg[0x2129];
+	if((a & INVW1) != 0)
+		w1 = !w1;
+	if((a & INVW2) != 0)
+		w2 = !w2;
+	if((a & (WIN1|WIN2)) != (WIN1|WIN2))
+		return (a & WIN1) != 0 ? w1 : w2;
+	a = reg[0x212a + (n >> 2)] >> ((n & 3) << 1);
+	switch(a & 3){
+	case 1: return w1 & w2;
+	case 2: return w1 ^ w2;
+	case 3: return w1 ^ w2 ^ 1;
+	}
+	return w1 | w2;
+}
+
+static void
+npixel(int n, int v, int pri)
+{
+	int a;
+	
+	a = 1<<n;
+	if((reg[TM] & a) != 0 && pri > pixelpri[0] && ((reg[TMW] & a) == 0 || !window(n))){
+		pixelcol[0] = v;
+		pixelpri[0] = pri;
+	}
+	if((reg[TS] & a) != 0 && pri > pixelpri[1] && ((reg[TSW] & a) == 0 || !window(n))){
+		pixelcol[1] = v;
+		pixelpri[1] = pri;
+	}
+}
+
+static void
+spixel(int n, int v, int pri)
+{
+	int a;
+	
+	a = 1<<n;
+	if((reg[TS] & a) != 0 && pri > pixelpri[1] && ((reg[TSW] & a) == 0 || !window(n))){
+		pixelcol[1] = v;
+		pixelpri[1] = pri;
+	}
+}
+
+static void
+mpixel(int n, int v, int pri)
+{
+	int a;
+	
+	a = 1<<n;
+	if((reg[TM] & a) != 0 && pri > pixelpri[0] && ((reg[TMW] & a) == 0 || !window(n))){
+		pixelcol[0] = v;
+		pixelpri[0] = pri;
+	}
+}
+
+static void (*pixel)(int, int, int) = npixel;
+
+static u16int
+tile(int n, int tx, int ty)
+{
+	int a;
+	u16int ta;
+	u16int t;
+
+	a = reg[0x2107 + n];
+	ta = ((a & ~3) << 9) + ((tx & 0x1f) << 1) + ((ty & 0x1f) << 6);
+	if((a & 1) != 0)
+		ta += (tx & 0x20) << 6;
+	if((a & 2) != 0)
+		ta += (ty & 0x20) << (6 + (a & 1));
+	t = vram[ta++];
+	return t | vram[ta] << 8;
+}
+
+static void
+chr(int n, int nb, int w, int h, u16int t, int x, int y, u32int c[])
+{
+	u16int a;
+
+	if(w == 16)
+		t += (x >> 3 ^ t >> 14) & 1;
+	if(h == 16){
+		if(y >= 8){
+			t += (~t >> 11) & 16;
+			y -= 8;
+		}else
+			t += (t >> 11) & 16;
+	}
+	if((t & 0x8000) != 0)
+		y = 7 - y;
+	a = reg[0x210b + (n >> 1)];
+	if((n & 1) != 0)
+		a >>= 4;
+	else
+		a &= 0xf;
+	a = (a << 13) + (t & 0x3ff) * 8 * nb + y * 2;
+	c[0] = vram[a++];
+	c[0] |= vram[a] << 8;
+	if(nb != 2){
+		a += 15;
+		c[0] |= vram[a++] << 16;
+		c[0] |= vram[a] << 24;
+		if(nb == 8){
+			a += 15;
+			c[1] = vram[a++];
+			c[1] |= vram[a] << 8;
+			a += 15;
+			c[1] |= vram[a++] << 16;
+			c[1] |= vram[a] << 24;
+		}
+	}
+}
+
+static int
+palette(int n, int p)
+{
+	switch(mode){
+	case 0:
+		return p << 2 | n << 5;
+	case 1:
+		if(n >= 2)
+			return p << 2;
+	case 2:
+	case 6:
+		return p << 4;
+	case 5:
+		if(n == 0)
+			return p << 4;
+		return p << 2;
+	case 3:
+		if(n != 0)
+			return p << 4;
+	case 4:
+		if(n != 0)
+			return p << 2;
+		if((reg[CGWSEL] & DIRCOL) != 0)
+			return 0x10000;
+	}
+	return 0;
+}
+
+static void
+shift(u32int *c, int nb, int n, int d)
+{
+	if(d){
+		c[0] >>= n;
+		if(nb == 8)
+			c[1] >>= n;
+	}else{
+		c[0] <<= n;
+		if(nb == 8)
+			c[1] <<= n;
+	}
+}
+
+static u8int
+bgpixel(u32int *c, int nb, int d)
+{
+	u8int v;
+	
+	if(d){
+		v = c[0] & 1 | c[0] >> 7 & 2;
+		if(nb != 2){
+			v |= c[0] >> 14 & 4 | c[0] >> 21 & 8;
+			if(nb == 8){
+				v |= c[1] << 4 & 16 | c[1] >> 3 & 32 | c[1] >> 10 & 64 | c[1] >> 17 & 128;
+				c[1] >>= 1;
+			}
+		}
+		c[0] >>= 1;
+	}else{
+		v = c[0] >> 7 & 1 | c[0] >> 14 & 2;
+		if(nb != 2){
+			v |= c[0] >> 21 & 4 | c[0] >> 28 & 8;
+			if(nb == 8){
+				v |= c[1] >> 3 & 16 | c[1] >> 10 & 32 | c[1] >> 17 & 64 | c[1] >> 24 & 128;
+				c[1] <<= 1;
+			}
+		}
+		c[0] <<= 1;
+	}
+	return v;
+}
+
+static struct bgctxt {
+	u8int w, h, wsh, hsh, nb, pri[2];
+	u16int tx, ty, tnx, tny;
+	u16int t;
+	u32int c[2];
+	int pal;
+	u8int msz, mv, mx;
+	u16int otx, oty, otny;
+} bgctxts[4];
+
+static void
+bginit(int n, int nb, int prilo, int prihi)
+{
+	struct bgctxt *p;
+	int sx, sy;
+
+	p = bgctxts + n;
+	p->hsh = ((reg[BGMODE] & (1<<(4+n))) != 0) ? 4 : 3;
+	p->wsh = mode >= 5 ? 4 : p->hsh;
+	p->h = 1<<p->hsh;
+	p->w = 1<<p->wsh;
+	p->nb = nb;
+	p->pri[0] = prilo;
+	p->pri[1] = prihi;
+	sx = hofs[n];
+	sy = vofs[n] + ppuy;
+	if(reg[MOSAIC] != 0 && (reg[MOSAIC] & (1<<n)) != 0){
+		p->msz = (reg[MOSAIC] >> 4) + 1;
+		if(p->msz != 1){
+			sx -= p->mx = sx % p->msz;
+			sy -= sy % p->msz;
+		}
+	}else
+		p->msz = 1;
+	if(mode >= 5)
+		sx <<= 1;
+redo:
+	p->tx = sx >> p->wsh;
+	p->tnx = sx & (p->w - 1);
+	p->ty = sy >> p->hsh;
+	p->tny = sy & (p->h - 1);
+	p->t = tile(n, p->tx, p->ty);
+	chr(n, nb, p->w, p->h, p->t, p->tnx, p->tny, p->c);
+	p->pal = palette(n, p->t >> 10 & 7);
+	if((p->tnx & 7) != 0)
+		shift(p->c, nb, p->tnx & 7, p->t & 0x4000);
+	if(p->msz != 1 && p->mx != 0 && sx % p->msz == 0){
+		p->mv = bgpixel(p->c, nb, p->t & 0x4000);
+		if(p->tnx + p->mx >= 8){
+			sx += p->mx;
+			goto redo;
+		}else if(p->mx > 1)
+			shift(p->c, nb, p->mx - 1, p->t & 0x4000);
+	}
+
+}
+
+static void
+bg(int n)
+{
+	struct bgctxt *p;
+	u8int v;
+
+	p = bgctxts + n;
+	v = bgpixel(p->c, p->nb, p->t & 0x4000);
+	if(p->msz != 1)
+		if(p->mx++ == 0)
+			p->mv = v;
+		else{
+			if(p->mx == p->msz)
+				p->mx = 0;
+			v = p->mv;
+		}
+	if(v != 0)
+		pixel(n, p->pal + v, p->pri[(p->t & 0x2000) != 0]);
+	if(++p->tnx == p->w){
+		p->tx++;
+		p->tnx = 0;
+		p->t = tile(n, p->tx, p->ty);
+		p->pal = palette(n, p->t >> 10 & 7);
+	}
+	if((p->tnx & 7) == 0)
+		chr(n, p->nb, p->w, p->h, p->t, p->tnx, p->tny, p->c);
+}
+
+static void
+optinit(void)
+{
+	struct bgctxt *p;
+	
+	p = bgctxts + 2;
+	p->hsh = (reg[BGMODE] & (1<<6)) != 0 ? 4 : 3;
+	p->wsh = mode >= 5 ? 4 : p->hsh;
+	p->w = 1<<p->wsh;
+	p->h = 1<<p->hsh;
+	p->tnx = hofs[2] & (p->w - 1);
+	p->tx = hofs[2] >> p->wsh;
+	bgctxts[0].otx = bgctxts[1].otx = 0xffff;
+	bgctxts[0].oty = bgctxts[0].ty;
+	bgctxts[0].otny = bgctxts[0].tny;
+	bgctxts[1].oty = bgctxts[1].ty;
+	bgctxts[1].otny = bgctxts[1].tny;
+}
+
+static void
+opt(void)
+{
+	struct bgctxt *p;
+	u16int hval, vval;
+	int sx, sy;
+	
+	p = bgctxts + 2;
+	if(++p->tnx == p->w){
+		if(mode == 4){
+			hval = tile(2, p->tx, vofs[2] >> p->hsh);
+			if((hval & 0x8000) != 0){
+				vval = hval;
+				hval = 0;
+			}else
+				vval = 0;
+		}else{
+			hval = tile(2, p->tx, vofs[2] >> p->hsh);
+			vval = tile(2, p->tx, (vofs[2]+8) >> p->hsh);
+		}
+		sx = (rx & ~7) + (hval & 0x1ff8);
+		sy = ppuy + (vval & 0x1fff);
+		if((vval & 0x2000) != 0){
+			bgctxts[0].oty = sy >> bgctxts[0].hsh;
+			bgctxts[0].otny = sy & (bgctxts[0].h - 1);
+		}else{
+			bgctxts[0].oty = bgctxts[0].ty;
+			bgctxts[0].otny = bgctxts[0].tny;
+		}
+		if((vval & 0x4000) != 0){
+			bgctxts[1].oty = sy >> bgctxts[1].hsh;
+			bgctxts[1].otny = sy & (bgctxts[1].h - 1);
+		}else{
+			bgctxts[1].oty = bgctxts[1].ty;
+			bgctxts[1].otny = bgctxts[1].tny;
+		}
+		if((hval & 0x2000) != 0)
+			bgctxts[0].otx = sx >> bgctxts[0].wsh;
+		else
+			bgctxts[0].otx = 0xffff;
+		if((hval & 0x4000) != 0)
+			bgctxts[1].otx = sx >> bgctxts[1].wsh;
+		else
+			bgctxts[1].otx = 0xffff;
+		p->tnx = 0;
+		p->tx++;
+	}
+}
+
+static void
+bgopt(int n)
+{
+	struct bgctxt *p;
+	u8int v;
+	
+	p = bgctxts + n;
+	v = bgpixel(p->c, p->nb, p->t & 0x4000);
+	if(p->msz != 1)
+		if(p->mx++ == 0)
+			p->mv = v;
+		else{
+			if(p->mx == p->msz)
+				p->mx = 0;
+			v = p->mv;
+		}
+	if(v != 0)
+		pixel(n, p->pal + v, p->pri[(p->t & 0x2000) != 0]);
+	if(++p->tnx == p->w){
+		p->tx++;
+		p->tnx = 0;
+	}
+	if((p->tnx & 7) == 0){
+		p->t = tile(n, p->otx == 0xffff ? p->tx : p->otx, p->oty);
+		p->pal = palette(n, p->t >> 10 & 7);
+		chr(n, p->nb, p->w, p->h, p->t, p->tnx, p->otny, p->c);
+	}
+}
+
+struct bg7ctxt {
+	int x, y, x0, y0;
+	u8int msz, mx, mv;
+} b7[2];
+
+void
+calc7(void)
+{
+	s16int t;
+
+	if((reg[0x2105] & 7) != 7)
+		return;
+	t = hofs[4] - m7[M7X];
+	if((t & 0x2000) != 0)
+		t |= ~0x3ff;
+	else
+		t &= 0x3ff;
+	b7->x0 = (t * m7[M7A]) & ~63;
+	b7->y0 = (t * m7[M7C]) & ~63;
+	t = vofs[4] - m7[M7Y];
+	if((t & 0x2000) != 0)
+		t |= ~0x3ff;
+	else
+		t &= 0x3ff;
+	b7->x0 += (t * m7[M7B]) & ~63;
+	b7->y0 += (t * m7[M7D]) & ~63;
+	b7->x0 += m7[M7X] << 8;
+	b7->y0 += m7[M7Y] << 8;
+}
+
+static void
+bg7init(int n)
+{
+	u8int m, y;
+	struct bg7ctxt *p;
+	
+	p = b7 + n;
+	m = reg[M7SEL];
+	y = ppuy;
+	p->msz = 1;
+	if((reg[MOSAIC] & 1) != 0){
+		p->msz = (reg[MOSAIC] >> 4) + 1;
+		if(p->msz != 1)
+			y -= y % p->msz;
+	}
+	if(n == 1)
+		if((reg[MOSAIC] & 2) != 0)
+			p->msz = (reg[MOSAIC] >> 4) + 1;
+		else
+			p->msz = 1;
+	if((m & 2) != 0)
+		y = 255 - y;
+	p->x = b7->x0 + ((m7[M7B] * y) & ~63);
+	p->y = b7->y0 + ((m7[M7D] * y) & ~63);
+	if((m & 1) != 0){
+		p->x += 255 * m7[M7A];
+		p->y += 255 * m7[M7C];
+	}
+}
+
+static void
+bg7(int n)
+{
+	u16int x, y;
+	u8int m, v, t;
+	struct bg7ctxt *p;
+
+	p = b7 + n;
+	m = reg[M7SEL];
+	x = p->x >> 8;
+	y = p->y >> 8;
+	if((m & 0x80) == 0){
+		x &= 1023;
+		y &= 1023;
+	}else if(x > 1023 || y > 1023){
+		if((m & 0x40) != 0){
+			t = 0;
+			goto lookup;
+		}
+		v = 0;
+		goto end;
+	}
+	t = vram[x >> 2 & 0xfe | y << 5 & 0x7f00];
+lookup:
+	v = vram[t << 7 | y << 4 & 0x70 | x << 1 & 0x0e | 1];
+end:
+	if(p->msz != 1){
+		if(p->mx == 0)
+			p->mv = v;
+		else
+			v = p->mv;
+		if(++p->mx == p->msz)
+			p->mx = 0;
+	}
+	if(n == 1){
+		if((v & 0x7f) != 0)
+			if((v & 0x80) != 0)
+				pixel(1, v & 0x7f, 0x71);
+			else
+				pixel(1, v, 0x11);
+	}else if(v != 0)
+		pixel(0, v, 0x40);
+	if((m & 1) != 0){
+		p->x -= m7[M7A];
+		p->y -= m7[M7C];
+	}else{
+		p->x += m7[M7A];
+		p->y += m7[M7C];
+	}
+}
+
+static void
+bgsinit(void)
+{
+	static int bitch[8];
+
+	pixel = npixel;
+	switch(mode){
+	case 0:
+		bginit(0, 2, 0x80, 0xb0);
+		bginit(1, 2, 0x71, 0xa1);
+		bginit(2, 2, 0x22, 0x52);
+		bginit(3, 2, 0x13, 0x43);
+		break;
+	case 1:
+		bginit(0, 4, 0x80, 0xb0);
+		bginit(1, 4, 0x71, 0xa1);
+		bginit(2, 2, 0x12, (reg[BGMODE] & 8) != 0 ? 0xd2 : 0x42);
+		break;
+	case 2:
+		bginit(0, 4, 0x40, 0xa0);
+		bginit(1, 4, 0x11, 0x71);
+		optinit();
+		break;
+	case 3:
+		bginit(0, 8, 0x40, 0xa0);
+		bginit(1, 4, 0x11, 0x71);
+		break;
+	case 4:
+		bginit(0, 8, 0x40, 0xa0);
+		bginit(1, 2, 0x11, 0x71);
+		optinit();
+		break;
+	case 5:
+		bginit(0, 4, 0x40, 0xa0);
+		bginit(1, 2, 0x11, 0x71);
+		break;
+	case 6:
+		bginit(0, 4, 0x40, 0xa0);
+		optinit();
+		break;
+	case 7:
+		bg7init(0);
+		if((reg[SETINI] & EXTBG) != 0)
+			bg7init(1);
+		break;
+	default:
+		bgctxts[0].w = bgctxts[1].w = 0;
+		if(bitch[mode]++ == 0)
+			print("bg mode %d not implemented\n", mode);
+	}
+}
+
+static void
+bgs(void)
+{
+	switch(mode){
+	case 0:
+		bg(0);
+		bg(1);
+		bg(2);
+		bg(3);
+		break;
+	case 1:
+		bg(0);
+		bg(1);
+		bg(2);
+		break;
+	case 2:
+	case 4:
+		opt();
+		bgopt(0);
+		bgopt(1);
+		break;
+	case 3:
+		bg(0);
+		bg(1);
+		break;
+	case 5:
+		pixel = spixel;
+		bg(0);
+		bg(1);
+		pixel = mpixel;
+		bg(0);
+		bg(1);
+		pixel = npixel;
+		break;
+	case 6:
+		opt();
+		pixel = spixel;
+		bgopt(0);
+		pixel = mpixel;
+		bgopt(0);
+		pixel = npixel;
+		break;
+	case 7:
+		bg7(0);
+		if((reg[SETINI] & EXTBG) != 0)
+			bg7(1);
+		break;
+	}
+}
+
+static void
+sprites(void)
+{
+	static struct {
+		short x;
+		u8int y, i, c, sx, sy;
+		u16int t0, t1;
+	} s[32], *sp;
+	static struct {
+		short x;
+		u8int sx, i, c, pal, pri;
+		u32int *ch;
+	} t[32], *tp;
+	static u32int ch[34];
+	static u8int *p, q;
+	static int n, m;
+	static int *sz;
+	static int szs[] = {
+		8, 8, 16, 16, 8, 8, 32, 32,
+		8, 8, 64, 64, 16, 16, 32, 32,
+		16, 16, 64, 64, 32, 32, 64, 64,
+		16, 32, 32, 64, 16, 32, 32, 32
+	};
+	static u16int base[2];
+	u8int dy, v, col, pri0, pri1, prio;
+	u16int a;
+	u32int w, *cp;
+	int i, nt, dx;
+
+	if(rx == 0){
+		n = 0;
+		sp = s;
+		sz = szs + ((reg[OBSEL] & 0xe0) >> 3);
+		base[0] = (reg[OBSEL] & 0x07) << 14;
+		base[1] = base[0] + (((reg[OBSEL] & 0x18) + 8) << 10);
+	}
+	if((rx & 1) == 0){
+		p = oam + 2 * rx;
+		if(p[1] == 0xf0)
+			goto nope;
+		q = (oam[512 + (rx >> 3)] >> (rx & 6)) & 3;
+		dy = ppuy - p[1];
+		sp->sx = sz[q & 2];
+		sp->sy = sz[(q & 2) + 1];
+		if(dy >= sp->sy)
+			goto nope;
+		sp->x = p[0];
+		if((q & 1) != 0)
+			sp->x |= 0xff00;
+		if(sp->x <= -(short)sp->sx && sp->x != -256)
+			goto nope;
+		if(n == 32){
+			reg[0x213e] |= 0x40;
+			goto nope;
+		}
+		sp->i = rx >> 1;
+		sp->y = p[1];
+		sp->c = p[3];
+		sp->t0 = p[2] << 5;
+		sp->t1 = base[sp->c & 1];
+		sp++;
+		n++;
+	}
+nope:
+	if(ppuy != 0){
+		col = 0;
+		pri0 = 0;
+		pri1 = 128;
+		if((reg[OAMADDH] & 0x80) != 0)
+			prio = oamaddr >> 2;
+		else
+			prio = 0;
+		for(i = 0, tp = t; i < m; i++, tp++){
+			dx = rx - tp->x;
+			if(dx < 0 || dx >= tp->sx)
+				continue;
+			w = *tp->ch;
+			if((tp->c & 0x40) != 0){
+				v = w & 1 | w >> 7 & 2 | w >> 14 & 4 | w >> 21 & 8;
+				*tp->ch = w >> 1;
+			}else{
+				v = w >> 7 & 1 | w >> 14 & 2 | w >> 21 & 4 | w >> 28 & 8;
+				*tp->ch = w << 1;
+			}
+			if((dx & 7) == 7)
+				tp->ch++;
+			nt = (tp->i - prio) & 0x7f;
+			if(v != 0 && nt < pri1){
+				col = tp->pal + v;
+				pri0 = tp->pri;
+				pri1 = nt;
+			}
+		}
+		if(col > 0)
+			pixel(OBJ, col, pri0);
+	}
+	if(rx == 255){
+		cp = ch;
+		m = n;
+		for(sp = s + n - 1, tp = t + n - 1; sp >= s; sp--, tp--){
+			tp->x = sp->x;
+			tp->sx = 0;
+			tp->c = sp->c;
+			tp->pal = 0x80 | sp->c << 3 & 0x70;
+			tp->pri = 3 * (0x10 + (sp->c & 0x30));
+			if((tp->c & 8) != 0)
+				tp->pri |= OBJ;
+			else
+				tp->pri |= OBJNC;
+			tp->ch = cp;
+			tp->i = sp->i;
+			nt = sp->sx >> 3;
+			dy = ppuy - sp->y;
+			if((sp->c & 0x80) != 0)
+				dy = sp->sy - 1 - dy;
+			a = sp->t0 | (dy & 7) << 1;
+			if(dy >= 8)
+				a += (dy & ~7) << 6;
+			if(sp->x < 0 && (i = (-sp->x >> 3)) != 0){
+				if((sp->c & 0x40) != 0)
+					a -= i << 5;
+				else
+					a += i << 5;
+				nt -= i;
+				tp->sx += i << 3;
+			}
+			if((sp->c & 0x40) != 0){
+				a += sp->sx * 4;
+				for(i = 0; i < nt; i++){
+					if(cp < ch + nelem(ch)){
+						w  = vram[sp->t1 | (a -= 16) & 0x1fff] << 16;
+						w |= vram[sp->t1 | (a + 1) & 0x1fff] << 24;
+						w |= vram[sp->t1 | (a -= 16) & 0x1fff] << 0;
+						w |= vram[sp->t1 | (a + 1) & 0x1fff] << 8;
+						*cp++ = w;
+						tp->sx += 8;
+					}else
+						reg[0x213e] |= 0x80;
+				}
+			}else
+				for(i = 0; i < nt; i++){
+					if(cp < ch + nelem(ch)){
+						w  = vram[sp->t1 | a & 0x1fff];
+						w |= vram[sp->t1 | ++a & 0x1fff] << 8;
+						w |= vram[sp->t1 | (a += 15) & 0x1fff] << 16;
+						w |= vram[sp->t1 | ++a & 0x1fff] << 24;
+						*cp++ = w;
+						tp->sx += 8;
+						a += 15;
+					}else
+						reg[0x213e] |= 0x80;
+				}
+			if(sp->x < 0 && (i = (-sp->x) & 7) != 0)
+				if((sp->c & 0x40) != 0)
+					*tp->ch >>= i;
+				else
+					*tp->ch <<= i;
+		}
+	}
+}
+
+static u16int
+colormath(int n)
+{
+	u16int v, w, r, g, b;
+	u8int m, m2, div;
+	int cw;
+	
+	m = reg[CGWSEL];
+	m2 = reg[CGADSUB];
+	cw = -1;
+	switch(m >> 6){
+	default: v = 1; break;
+	case 1: v = cw = window(COL); break;
+	case 2: v = !(cw = window(COL)); break;
+	case 3: v = 0; break;
+	}
+	if(v){
+		if((pixelcol[n] & 0x10000) != 0)
+			v = pixelcol[n];
+		else
+			v = cgram[pixelcol[n] & 0xff];
+	}
+	if((m2 & (1 << (pixelpri[n] & 0xf))) == 0)
+		return v;
+	switch((m >> 4) & 3){
+	case 0: break;
+	case 1: if(cw < 0) cw = window(COL); if(!cw) return v; break;
+	case 2: if(cw < 0) cw = window(COL); if(cw) return v; break;
+	default: return v;
+	}
+	div = (m2 & 0x40) != 0;
+	if((m & 2) != 0){
+		if((pixelcol[1-n] & 0x10000) != 0)
+			w = pixelcol[1-n];
+		else
+			w = cgram[pixelcol[1-n] & 0xff];
+		div = div && (pixelpri[1-n] & 0xf) != COL;
+	}else
+		w = subcolor;
+	if((m2 & 0x80) != 0){
+		r = (v & 0x7c00) - (w & 0x7c00);
+		g = (v & 0x03e0) - (w & 0x03e0);
+		b = (v & 0x001f) - (w & 0x001f);
+		if(r > 0x7c00) r = 0;
+		if(g > 0x03e0) g = 0;
+		if(b > 0x001f) b = 0;
+		if(div){
+			r = (r >> 1) & 0xfc00;
+			g = (g >> 1) & 0xffe0;
+			b >>= 1;
+		}
+		return r | g | b;
+	}else{
+		r = (v & 0x7c00) + (w & 0x7c00);
+		g = (v & 0x03e0) + (w & 0x03e0);
+		b = (v & 0x001f) + (w & 0x001f);
+		if(div){
+			r = (r >> 1) & 0xfc00;
+			g = (g >> 1) & 0xffe0;
+			b >>= 1;
+		}
+		if(r > 0x7c00) r = 0x7c00;
+		if(g > 0x03e0) g = 0x03e0;
+		if(b > 0x001f) b = 0x001f;
+		return r | g | b;
+	}
+}
+
+void
+ppustep(void)
+{
+	int yvbl;
+
+	mode = reg[BGMODE] & 7;
+	bright = reg[INIDISP] & 0xf;
+	hires = mode >= 5 && mode < 7 || (reg[SETINI] & 8) != 0;
+	yvbl = (reg[SETINI] & OVERSCAN) != 0 ? 0xf0 : 0xe1;
+
+	if(ppux >= XLEFT && ppux <= XRIGHT && ppuy < 0xf0){
+		rx = ppux - XLEFT;
+		if(ppuy < yvbl && (reg[INIDISP] & 0x80) == 0){
+			pixelcol[0] = 0;
+			pixelpri[0] = COL;
+			pixelcol[1] = 0x10000 | subcolor;
+			pixelpri[1] = COL;	
+			bgs();
+			sprites();
+			if(ppuy != 0)
+				if(hires){
+					pixeldraw(rx, ppuy - 1, colormath(1), 0);
+					pixeldraw(rx, ppuy - 1, colormath(0), 1);
+				}else
+					pixeldraw(rx, ppuy - 1, colormath(0), 0);
+		}else if(ppuy != 0)
+			pixeldraw(rx, ppuy - 1, ppuy >= yvbl ? 0x6739 : 0, -1);
+	}
+
+	if(ppux == 134)
+		cpupause = 1;
+	if(ppux == 0x116 && ppuy <= yvbl)
+		hdma |= reg[0x420c];
+	if((reg[NMITIMEN] & HCNTIRQ) != 0 && htime+4 == ppux && ((reg[NMITIMEN] & VCNTIRQ) == 0 || vtime == ppuy))
+		irq |= IRQPPU;
+	if(++ppux >= 340){
+		ppux = 0;
+		if(++ppuy >= 262){
+			ppuy = 0;
+			reg[RDNMI] &= ~VBLANK;
+			reg[0x213e] = 1;
+			reg[0x213f] ^= 0x80;
+			hdma = reg[0x420c]<<8;
+			flush();
+		}
+		if(ppuy < yvbl)
+			bgsinit();
+		if(ppuy == yvbl){
+			reg[RDNMI] |= VBLANK;
+			if((reg[NMITIMEN] & VBLANK) != 0)
+				nmi = 2;
+			if((reg[INIDISP] & 0x80) == 0)
+				oamaddr = reg[0x2102] << 1 | (reg[0x2103] & 1) << 9;
+			if((reg[NMITIMEN] & AUTOJOY) != 0){
+				memwrite(0x4016, 1);
+				memwrite(0x4016, 0);
+				reg[0x4218] = keylatch >> 16;
+				reg[0x4219] = keylatch >> 24;
+				keylatch = keylatch << 16 | 0xffff;
+			}
+		}
+		if((reg[NMITIMEN] & (HCNTIRQ|VCNTIRQ)) == VCNTIRQ && vtime == ppuy)
+			irq |= IRQPPU;
+	}
+}