shithub: riscv

Download patch

ref: 77f3fa19de87be8ded57a038cab60b240b191dab
parent: 709e78b9f9a76658cc6c704176ebf8f5821ee9d2
author: aiju <devnull@localhost>
date: Sat Sep 27 16:50:20 EDT 2014

games/gba: slowly working (no pun intended)

diff: cannot open b/sys/src/games/gba//null: file does not exist: 'b/sys/src/games/gba//null'
--- /dev/null
+++ b/sys/src/games/gba/cpu.c
@@ -1,0 +1,1269 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+enum {
+	FLAGN = 1<<31,
+	FLAGZ = 1<<30,
+	FLAGC = 1<<29,
+	FLAGV = 1<<28,
+	FLAGT = 1<<5,
+	FLAGI = 1<<7,
+	
+	MUSR = 0x10,
+	MFIQ = 0x11,
+	MIRQ = 0x12,
+	MSVC = 0x13,
+	MABT = 0x17,
+	MUND = 0x1b,
+	MSYS = 0x1f,
+	MODE = 0x1f,
+	
+	R13USR = 0, R14USR, R13FIQ, R14FIQ,
+	R13SVC, R14SVC, R13ABT, R14ABT,
+	R13IRQ, R14IRQ, R13UND, R14UND,
+	SPSRFIQ, SPSRSVC, SPSRABT, SPSRIRQ, SPSRUND,
+	R8USR, R9USR, R10USR, R11USR, R12USR,
+	R8FIQ, R9FIQ, R10FIQ, R11FIQ, R12FIQ
+};
+u32int r[16], cpsr, spsr;
+u32int saver[R12FIQ+1];
+u32int curpc;
+int irq;
+
+u32int instr0, instr1, pipel = -1;
+int cyc, trace;
+
+#define pipeflush() {io(); pipel = -1;}
+#define io() cyc++
+
+static int steparm(void);
+static int stepthumb(void);
+int (*step)(void);
+
+void
+undefined(u32int instr)
+{
+	if((cpsr & FLAGT) != 0)
+		sysfatal("undefined opcode %#.4ux (pc=%#.8ux)", (u16int)instr, curpc);
+	else
+		sysfatal("undefined opcode %#.8ux (pc=%#.8ux)", instr, curpc);
+}
+
+int
+cond(int n, u32int instr)
+{
+	switch(n){
+	case 0: return (cpsr & FLAGZ) != 0;
+	case 1: return (cpsr & FLAGZ) == 0;
+	case 2: return (cpsr & FLAGC) != 0;
+	case 3: return (cpsr & FLAGC) == 0;
+	case 4: return (cpsr & FLAGN) != 0;
+	case 5: return (cpsr & FLAGN) == 0;
+	case 6: return (cpsr & FLAGV) != 0;
+	case 7: return (cpsr & FLAGV) == 0;
+	case 8: return (cpsr & (FLAGC|FLAGZ)) == FLAGC;
+	case 9: return (cpsr & (FLAGC|FLAGZ)) != FLAGC;
+	case 10: return ((cpsr ^ cpsr << 3) & FLAGN) == 0;
+	case 11: return ((cpsr ^ cpsr << 3) & FLAGN) != 0;
+	case 12: return ((cpsr ^ cpsr << 3) & (FLAGN|FLAGZ)) == 0;
+	case 13: return ((cpsr ^ cpsr << 3) & (FLAGN|FLAGZ)) != 0;
+	case 14: return 1;
+	}
+	undefined(instr);
+	return 0;
+}
+
+static void
+setcpsr(int n)
+{
+	if((n & FLAGT) != 0)
+		step = stepthumb;
+	else
+		step = steparm;
+	if((cpsr & MODE) == (n & MODE)){
+		cpsr = n;
+		return;
+	}
+	switch(cpsr & MODE){
+	case MUSR:
+	case MSYS:
+		saver[R13USR] = r[13];
+		saver[R14USR] = r[14];
+		break;
+	case MFIQ:
+		saver[R13FIQ] = r[13];
+		saver[R14FIQ] = r[14];
+		saver[SPSRFIQ] = spsr;
+		break;
+	case MSVC:
+		saver[R13SVC] = r[13];
+		saver[R14SVC] = r[14];
+		saver[SPSRSVC] = spsr;
+		break;
+	case MABT:
+		saver[R13ABT] = r[13];
+		saver[R14ABT] = r[14];
+		saver[SPSRABT] = spsr;
+		break;
+	case MIRQ:
+		saver[R13IRQ] = r[13];
+		saver[R14IRQ] = r[14];
+		saver[SPSRIRQ] = spsr;
+		break;
+	case MUND:
+		saver[R13UND] = r[13];
+		saver[R14UND] = r[14];
+		saver[SPSRUND] = spsr;
+		break;
+	}
+	switch(n & MODE){
+	case MUSR:
+	case MSYS:
+		r[13] = saver[R13USR];
+		r[14] = saver[R14USR];
+		break;
+	case MFIQ:
+		r[13] = saver[R13FIQ];
+		r[14] = saver[R14FIQ];
+		spsr = saver[SPSRFIQ];
+		break;
+	case MSVC:
+		r[13] = saver[R13SVC];
+		r[14] = saver[R14SVC];
+		spsr = saver[SPSRSVC];
+		break;
+	case MABT:
+		r[13] = saver[R13ABT];
+		r[14] = saver[R14ABT];
+		spsr = saver[SPSRABT];
+		break;
+	case MIRQ:
+		r[13] = saver[R13IRQ];
+		r[14] = saver[R14IRQ];
+		spsr = saver[SPSRIRQ];
+		break;
+	case MUND:
+		r[13] = saver[R13UND];
+		r[14] = saver[R14UND];
+		spsr = saver[SPSRUND];
+		break;
+	default:
+		sysfatal("invalid mode switch to %#x (pc=%#.8x)", n, curpc);
+	}
+	if((cpsr & MODE) == MFIQ){
+		memcpy(&saver[R8FIQ], &r[8], 5*4);
+		memcpy(&r[8], &saver[R8USR], 5*4);
+	}
+	if((n & MODE) == MFIQ){
+		memcpy(&saver[R8USR], &r[8], 5*4);
+		memcpy(&r[8], &saver[R8FIQ], 5*4);
+	}
+	cpsr = n;
+}
+
+static void
+interrupt(int src)
+{
+	u32int v;
+	
+	v = cpsr;
+	setcpsr(cpsr & ~(MODE|FLAGI|FLAGT) | FLAGI | src);
+	spsr = v;
+	switch(src){
+	case MIRQ:
+		if((v & FLAGT) != 0)
+			r[14] = r[15];
+		else
+			r[14] = r[15] - 4;
+		r[15] = 0x18;
+		break;
+	case MSVC:
+		if((v & FLAGT) != 0)
+			r[14] = r[15] - 2;
+		else
+			r[14] = r[15] - 4;
+		r[15] = 0x08;
+		break;
+	default:
+		sysfatal("unknown exception %x\n", src);
+	}
+	pipeflush();
+}
+
+static void
+mulspeed(u32int val)
+{
+	if((int)val < 0) val = ~val;
+	if((val >> 8) == 0)
+		cyc += 1;
+	else if((val >> 16) == 0)
+		cyc += 2;
+	else if((val >> 24) == 0)
+		cyc += 3;
+	else
+		cyc += 4;
+}
+
+static void
+armextra(u32int instr)
+{
+	int Rn, Rd, Rm, Rs, sh;
+	u32int addr, off, val;
+	u64int vall;
+	enum {
+		SIGN = 1<<6,
+		HALF = 1<<5,
+		LOAD = 1<<20,
+		WRBACK = 1<<21,
+		IMM = 1<<22,
+		ADD = 1<<23,
+		PRE = 1<<24,
+		
+		BYTE = 1<<22,
+		
+		LONG = 1<<23,
+		MSIGN = 1<<22,
+		ACC = 1<<21,
+		FLAGS = 1<<20,
+	};
+	
+	Rm = instr & 0xf;
+	Rn = instr >> 16 & 0xf;
+	Rd = instr >> 12 & 0xf;
+	if((instr & 0x60) == 0){
+		if((instr & 1<<24) != 0){
+			addr = r[Rn];
+			if((instr & 0x0ffffff0) == 0x012fff10){
+				r[14] = r[15] - 4;
+				r[15] = r[Rm];
+				setcpsr(cpsr | FLAGT);
+				pipeflush();
+			}else if((instr & BYTE) != 0){
+				io();
+				val = (u8int) memread(addr, 1, 0);
+				memwrite(addr, (u8int) r[Rm], 1);
+				r[Rd] = val;
+			}else{
+				io();
+				val = memread(addr & ~3, 4, 0);
+				if((addr & 3) != 0){
+					sh = (addr & 3) << 2;
+					val = val >> sh | val << 32 - sh;
+				}
+				memwrite(addr, r[Rm], 4);
+				r[Rd] = val;	
+			}
+		}else{
+			Rs = instr >> 8 & 0xf;
+			mulspeed(r[Rs]);
+			if((instr & LONG) != 0){
+				if((instr & ACC) != 0){
+					vall = (u64int)r[Rn] << 32 | r[Rd];
+					io();
+				}else
+					vall = 0;
+				io();
+				if((instr & MSIGN) == 0)
+					vall += ((u64int) r[Rs]) * r[Rm];
+				else
+					vall += (s64int) ((s32int) r[Rs]) * (s32int) r[Rm];
+				r[Rn] = vall >> 32;
+				r[Rd] = vall;
+				if((instr & FLAGS) != 0){
+					cpsr &= ~(FLAGN|FLAGZ|FLAGC);
+					if(vall == 0)
+						cpsr |= FLAGZ;
+					if((s64int)vall < 0)
+						cpsr |= FLAGN;
+				}
+			}else{
+				val = r[Rs] * r[Rm];
+				if((instr & ACC) != 0){
+					val += r[Rd];
+					io();
+				}
+				if((instr & FLAGS) != 0){
+					cpsr &= ~(FLAGN|FLAGZ|FLAGC);
+					if(val == 0)
+						cpsr |= FLAGZ;
+					if((int)val < 0)
+						cpsr |= FLAGN;
+				}
+				r[Rn] = val;
+			}
+		}
+		return;
+	}
+	if((instr & IMM) == 0)
+		off = r[Rm];
+	else
+		off = instr & 0xf | instr >> 4 & 0xf0;
+	if((instr & ADD) == 0)
+		off = -off;
+	addr = r[Rn];
+	if((instr & PRE) != 0)
+		addr += off;
+	switch(instr & (HALF|LOAD)){
+	case 0:
+		memwrite(addr, (u8int) r[Rd], 1);
+		break;
+	case HALF:
+		memwrite(addr & ~1, (u16int) r[Rd], 2);
+		break;
+	case LOAD:
+		io();
+		r[Rd] = (u8int) memread(addr, 1, 0);
+		if((instr & SIGN) != 0)
+			r[Rd] = (s8int) r[Rd];
+		break;
+	case LOAD|HALF:
+		io();
+		val = (u16int) memread(addr & ~1, 2, 0);
+		if((instr & SIGN) != 0)
+			val = (s16int) val;
+		if((addr & 1) != 0)
+			val = val << 8 | val >> 24;
+		r[Rd] = val;
+		break;
+	}
+	if((instr & PRE) == 0)
+		addr += off;
+	if((instr & (WRBACK|PRE)) != PRE && Rn != Rd)
+		r[Rn] = addr;
+}
+
+static void
+armsr(u32int instr)
+{
+	int Rd, Rs;
+	u32int op, op0;
+	
+	if((instr & 0x0fbf0fff) == 0x010f0000){
+		Rd = instr >> 12 & 0xf;
+		r[Rd] = (instr & 1<<22) != 0 ? spsr : cpsr;
+		return;
+	}
+	if((instr & 0x0fb0fff0) == 0x0120f000){
+		Rs = instr & 0xf;
+		op = r[Rs];
+	msr:
+		op0 = 0;
+		if((instr & 1<<16) != 0) op0 |= 0xff;
+		if((instr & 1<<17) != 0) op0 |= 0xff00;
+		if((instr & 1<<18) != 0) op0 |= 0xff0000;
+		if((instr & 1<<19) != 0) op0 |= 0xff000000;
+		if((instr & 1<<22) != 0)
+			spsr = spsr & ~op0 | op & op0;
+		else
+			setcpsr(cpsr & ~op0 | op & op0);
+		if((cpsr & FLAGT) != 0)
+			sysfatal("illegal MSR to CPSR (T bit set, val=%#.8ux, pc=%#.8ux)", cpsr, curpc);
+		return;
+	}
+	if((instr & 0x0fb0f000) == 0x0320f000){
+		op = (u8int) instr;
+		Rs = instr >> 7 & 0x1e;
+		op = op >> Rs | op << 32 - Rs;
+		goto msr;
+	}
+	if((instr & 0x0ffffff0) == 0x012fff10){
+		Rs = instr & 0xf;
+		op = r[Rs];
+		if((op & 1) != 0)
+			setcpsr(cpsr | FLAGT);
+		r[15] = op & ~1;
+		pipeflush();
+		return;
+	}
+	undefined(instr);
+}
+
+static void
+armalu(u32int instr)
+{
+	int Rn, Rd, Rs, Rm, oper, sbit;
+	u32int op, op0, res;
+	u64int res64;
+	int sh;
+	int cout;
+
+	if((instr & (1<<25|0x90)) == 0x90){
+		armextra(instr);
+		return;
+	}
+	Rn = instr >> 16 & 0xf;
+	Rd = instr >> 12 & 0xf;
+	Rs = instr >> 8 & 0xf;
+	if((instr & 1<<25) == 0){
+		Rm = instr & 0xf;
+		op = r[Rm];
+		if((instr & 1<<4) == 0)
+			sh = instr >> 7 & 0x1f;
+		else{
+			sh = (u8int) r[Rs];
+			if(Rm == 15)
+				op += 4; /* undocumented behaviour */
+		}
+		switch(instr >> 5 & 3){
+		default:
+			if(sh == 0)
+				cout = cpsr >> 29;
+			else if(sh < 32){
+				cout = op >> 32 - sh;
+				op = op << sh;
+			}else if(sh == 32){
+				cout = op;
+				op = 0;
+			}else
+				cout = op = 0;
+			break;
+		case 1:
+			if(sh == 0)
+				if((instr & 1<<4) != 0)
+					cout = cpsr >> 29;
+				else{
+					cout = op >> 31;
+					op = 0;
+				}
+			else if(sh < 32 && sh != 0){
+				cout = op >> sh - 1;
+				op = op >> sh;
+			}else if(sh == 32){
+				cout = op >> 31;
+				op = 0;
+			}else
+				cout = op = 0;
+			break;
+		case 2:
+			if(sh == 0){
+				if((instr & 1<<4) != 0)
+					cout = cpsr >> 29;
+				else
+					cout = op = -((int)op < 0);
+			}else if(sh < 32){
+				cout = op >> sh - 1;
+				op = ((int) op) >> sh;
+			}else
+				cout = op = -((int)op < 0);
+			break;
+		case 3:
+			if(sh == 0){
+				if((instr & 1<<4) != 0)
+					cout = cpsr >> 29;
+				else{
+					cout = op;
+					op = op >> 1 | (cpsr & FLAGC) << 2;
+				}
+			}else{
+				sh &= 31;
+				if(sh == 0)
+					cout = op >> 31;
+				else{
+					cout = op >> sh - 1;
+					op = op << 32 - sh | op >> sh;
+				}
+			}
+			break;
+		}
+		cyc++;
+	}else{
+		op = (u8int) instr;
+		Rs <<= 1;
+		if(Rs != 0){
+			op = op << 32 - Rs | op >> Rs;
+			cout = op >> 31;
+		}else
+			cout = cpsr >> 29;
+	}
+	sbit = instr & 1<<20;
+	op0 = r[Rn];
+	oper = instr >> 21 & 0xf;
+	SET(res64);
+	switch(oper){
+	default: case 0: case 8: res = op0 & op; break;
+	case 1: case 9: res = op0 ^ op; break;
+	case 2: case 10: res64 = (uvlong) op0 + ~op + 1; res = res64; break;
+	case 3: res64 = (uvlong) op + ~op0 + 1; res = res64; break;
+	case 4: case 11: res64 = (uvlong) op0 + op; res = res64; break;
+	case 5: res64 = (uvlong) op0 + op + (cpsr >> 29 & 1); res = res64; break;
+	case 6: res64 = (uvlong) op0 + ~op + (cpsr >> 29 & 1); res = res64; break;
+	case 7: res64 = (uvlong) op + ~op0 + (cpsr >> 29 & 1); res = res64; break;
+	case 12: res = op0 | op; break;
+	case 13: res = op; break;
+	case 14: res = op0 & ~op; break;
+	case 15: res = ~op; break;
+	}
+	if(sbit){
+		switch(oper){
+		case 2: case 6: case 10:
+			cpsr &= ~(FLAGC|FLAGN|FLAGZ|FLAGV);
+			if(res64 >> 32 != 0)
+				cpsr |= FLAGC;
+			if(((op0 ^ op) & (op0 ^ res) & 1<<31) != 0)
+				cpsr |= FLAGV;
+			break;
+		case 3: case 7:
+			cpsr &= ~(FLAGC|FLAGN|FLAGZ|FLAGV);
+			if(res64 >> 32 != 0)
+				cpsr |= FLAGC;
+			if(((op ^ op0) & (op ^ res) & 1<<31) != 0)
+				cpsr |= FLAGV;
+			break;
+		case 4: case 5: case 11:
+			cpsr &= ~(FLAGC|FLAGN|FLAGZ|FLAGV);
+			if(res64 >> 32 != 0)
+				cpsr |= FLAGC;
+			if((~(op ^ op0) & (op ^ res) & 1<<31) != 0)
+				cpsr |= FLAGV;
+			break;
+		default:
+			cpsr &= ~(FLAGC|FLAGN|FLAGZ);
+			if(cout & 1)
+				cpsr |= FLAGC;
+			break;
+		}
+		if(res == 0)
+			cpsr |= FLAGZ;
+		if((res & 1<<31) != 0)
+			cpsr |= FLAGN;
+	}
+	if(oper < 8 || oper > 11){
+		r[Rd] = res;
+		if(Rd == 15){
+			if(sbit)
+				setcpsr(spsr);
+			pipeflush();
+		}
+	}else if(!sbit){
+		if((instr & 1<<25) != 0)
+			cyc--;
+		armsr(instr);
+	}
+}
+
+static void
+armsingle(u32int instr)
+{
+	int op, Rn, Rd, Rm;
+	u32int off, addr, val, sh;
+	enum {
+		LOAD = 1<<0,
+		WRBACK = 1<<1,
+		BYTE = 1<<2,
+		ADD = 1<<3,
+		PRE = 1<<4,
+		REGOFF = 1<<5
+	};
+	
+	op = instr >> 20;
+	Rn = instr >> 16 & 0xf;
+	Rd = instr >> 12 & 0xf;
+	if((op & REGOFF) != 0){
+		Rm = instr & 0xf;
+		off = r[Rm];
+		if((instr & 0xfff0) != 0){
+			sh = instr >> 7 & 0x1f;
+			switch(instr >> 5 & 3){
+			case 0: off = off << sh; break;
+			case 1:
+				if(sh == 0)
+					off = 0;
+				else
+					off = off >> sh;
+				break;
+			case 2:
+				if(sh == 0)
+					off = -((int)off < 0);
+				else
+					off = ((int)off) >> sh;
+				break;
+			case 3:
+				if(sh == 0)
+					off = off >> 1 | (cpsr & FLAGC) << 2;
+				else	
+					off = off >> sh | off << 32 - sh;
+				break;
+			}
+		}
+	}else
+		off = instr & 0xfff;
+	if((op & ADD) == 0)
+		off = -off;
+	addr = r[Rn];
+	if((op & PRE) != 0)
+		addr += off;
+	io();
+	switch(op & (LOAD|BYTE)){
+	case 0:
+		memwrite(addr & ~3, r[Rd], 4);
+		break;
+	case BYTE:
+		memwrite(addr, r[Rd], 1);
+		break;
+	case LOAD:
+		val = memread(addr & ~3, 4, 0);
+		if((addr & 3) != 0){
+			sh = (addr & 3) << 3;
+			val = val >> sh | val << 32 - sh;
+		}
+		r[Rd] = val;
+		io();
+		if(Rd == 15)
+			pipeflush();
+		break;
+	case LOAD|BYTE:
+		r[Rd] = (u8int) memread(addr, 1, 0);
+		io();
+		if(Rd == 15)
+			pipeflush();
+		break;
+	}
+	if((op & PRE) == 0)
+		addr += off;
+	if((op & (WRBACK|PRE)) != PRE && Rn != Rd)
+		r[Rn] = addr;
+}
+
+static void
+armmulti(u32int instr)
+{
+	int i, Rn, pop, user;
+	u32int addr, val, *rp;
+	u16int bits;
+	int seq;
+	enum {
+		LOAD = 1<<20,
+		WRBACK = 1<<21,
+		USER = 1<<22,
+		UP = 1<<23,
+		PRE = 1<<24,
+	};
+	
+	Rn = instr >> 16 & 0xf;
+	addr = r[Rn] & ~3;
+	if((instr & LOAD) != 0)
+		io();
+	for(bits = instr, pop = 0; bits != 0; pop++)
+		bits &= bits - 1;
+	pop <<= 2;
+	user = (instr & (USER|1<<15)) == USER;
+	switch(instr & (PRE|UP)){
+	default:
+		val = addr - pop;
+		addr = val + 4;
+		break;
+	case PRE:
+		addr = val = addr - pop;
+		break;
+	case UP:
+		val = addr + pop;
+		break;
+	case UP|PRE:
+		val = addr + pop;
+		addr += 4;
+		break;
+	}
+	seq = 0;
+	for(i = 0; i < 16; i++){
+		if((instr & 1<<i) == 0)
+			continue;
+		if(user)
+			switch(i){
+			case 8: case 9: case 10: case 11: case 12:
+				if((cpsr & MODE) == MFIQ){
+					rp = &saver[R8USR + i - 8];
+					break;
+				}
+			default: rp = &r[i]; break;
+			case 13: rp = &saver[R13USR]; break;
+			case 14: rp = &saver[R14USR]; break;
+			}
+		else
+			rp = &r[i];
+		if((instr & LOAD) != 0)
+			*rp = memread(addr, 4, seq);
+		else
+			memwrite(addr, *rp, 4);
+		addr += 4;
+		seq = 1;
+	}
+	/* undocumented: if Rn is the first register set in a load, it's overwritten if writeback is specified */
+	if((instr & WRBACK) != 0 && ((instr & LOAD) == 0 || (instr & instr-1 & 1<<Rn) == 0))
+		r[Rn] = val;
+	if((instr & (LOAD|1<<15)) == (LOAD|1<<15)){
+		if((instr & USER) != 0)
+			setcpsr(spsr);
+		pipeflush();
+	}
+}
+
+static void
+armbranch(u32int instr)
+{
+	int a;
+	
+	a = instr & 0xffffff;
+	a = (a << 8) >> 6;
+	if((instr & 1<<24) != 0)
+		r[14] = r[15] - 4;
+	r[15] += a;
+	pipeflush();
+}
+
+static int
+steparm(void)
+{
+	int s;
+	u32int instr;
+
+	cyc = 0;
+	if((pipel & 2) != 0)
+		goto fetch;
+	if(irq && (cpsr & FLAGI) == 0){
+		interrupt(MIRQ);
+		return 1;
+	}
+	curpc = r[15] - 8;
+	instr = instr1;
+	if(trace)
+		print("A %.8ux %.8ux %.8ux %.8ux %.8ux | %.8ux %.8ux %.8ux %.8ux | %.8ux %.8ux %.8ux %.8ux\n", curpc, instr, cpsr, r[13], r[14], r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7]);
+	if(instr >> 28 != 0xe && !cond(instr >> 28, instr))
+		goto fetch;
+	switch(instr >> 24 & 0xf){
+	case 0: case 1: case 2: case 3:
+		armalu(instr);
+		break;
+	case 4: case 5: case 6: case 7:
+		armsingle(instr);
+		break;
+	case 8: case 9:
+		armmulti(instr);
+		break;
+	case 10: case 11:
+		armbranch(instr);
+		break;
+	case 15:
+		interrupt(MSVC);
+		break;
+	default:
+		undefined(instr);
+	}
+fetch:
+	instr1 = instr0;
+	s = step == steparm ? 4 : 2;
+	instr0 = memread(r[15], s, pipel != -1);
+	r[15] += s;
+	pipel <<= 1;
+	return cyc;
+}
+
+static void
+addflags(u32int a, u32int b, u32int c)
+{
+	u64int v;
+	
+	v = (u64int) a + b + c;
+	cpsr &= ~(FLAGN|FLAGZ|FLAGC|FLAGV);
+	if((u32int)v == 0)
+		cpsr |= FLAGZ;
+	cpsr |= v & FLAGN;
+	if(v >> 32 != 0)
+		cpsr |= FLAGC;
+	if((~(a ^ b) & (a ^ v) & 1<<31) != 0)
+		cpsr |= FLAGV;
+}
+
+static void
+nz(u32int v)
+{
+	cpsr &= ~(FLAGN|FLAGZ);
+	if(v == 0)
+		cpsr |= FLAGZ;
+	cpsr |= v & FLAGN;
+}
+
+static void
+thshadd(u16int instr)
+{
+	int Rd, Rs, off, op;
+	u32int val, a, b, cout;
+	
+	Rd = instr & 7;
+	Rs = instr >> 3 & 7;
+	off = instr >> 6 & 0x1f;
+	op = instr >> 11 & 3;
+	a = r[Rs];
+	switch(op){
+	case 0:
+		if(off == 0){
+			r[Rd] = val = a;
+			cout = cpsr >> 29;
+		}else{
+			r[Rd] = val = a << off;
+			cout = a >> 32 - off;
+		}
+		goto logflags;
+	case 1:
+		if(off == 0){
+			r[Rd] = val = 0;
+			cout = a >> 31;
+		}else{
+			r[Rd] = val = a >> off;
+			cout = a >> off - 1;
+		}
+		goto logflags;
+	case 2:
+		if(off == 0)
+			cout = r[Rd] = val = -((int)a < 0);
+		else{
+			r[Rd] = val = (int)a >> off;
+			cout = a >> off - 1;
+		}
+		goto logflags;
+	case 3:
+		break;
+	}
+	if((instr & 1<<10) == 0)
+		b = r[off & 7];
+	else
+		b = off & 7;
+	if((instr & 1<<9) != 0)
+		b = -b;
+	r[Rd] = a + b;
+	addflags(a, b, 0);
+	return;
+logflags:
+	cpsr &= ~(FLAGN|FLAGZ|FLAGC);
+	if(val == 0)
+		cpsr |= FLAGZ;
+	if((int)val < 0)
+		cpsr |= FLAGN;
+	if((cout & 1) != 0)
+		cpsr |= FLAGC;
+	
+}
+
+static void
+thaddimm(u16int instr)
+{
+	int Rd, b, op, a;
+	
+	b = instr & 0xff;
+	Rd = instr >> 8 & 7;
+	a = r[Rd];
+	op = instr >> 11 & 3;
+	switch(op){
+	case 0:
+		r[Rd] = b;
+		nz(b);
+		break;
+	case 1:
+		addflags(a, ~b, 1);
+		break;
+	case 2:
+		r[Rd] = a + b;
+		addflags(a, b, 0);
+		break;
+	case 3:
+		r[Rd] = a - b;
+		addflags(a, ~b, 1);
+		break;
+	}
+}
+
+static void
+thalu(u16int instr)
+{
+	int Rs, Rd;
+	u32int a, b, v, c;
+
+	switch(instr >> 10 & 3){
+	case 0:
+		Rd = instr & 7;
+		Rs = instr >> 3 & 7;
+		a = r[Rd];
+		b = r[Rs];
+		switch(instr >> 6 & 0xf){
+		case 0:
+			r[Rd] = v = a & b;
+			nz(v);
+			break;
+		case 1:
+			r[Rd] = v = a ^ b;
+			nz(v);
+			break;
+		case 2:
+			io();
+			v = a;
+			if(b != 0){
+				if(b < 32){
+					c = v >> 32 - b;
+					v <<= b;
+				}else if(b == 32){
+					c = v;
+					v = 0;
+				}else
+					c = v = 0;
+				cpsr = cpsr & ~FLAGC | c << 29 & FLAGC;
+			}
+			r[Rd] = v;
+			nz(v);
+			break;
+		case 3:
+			io();
+			v = a;
+			if(b != 0){
+				if(b < 32){
+					c = v >> b - 1;
+					v >>= b;
+				}else if(b == 32){
+					c = v >> 31;
+					v = 0;
+				}else
+					c = v = 0;
+				cpsr = cpsr & ~FLAGC | c << 29 & FLAGC;
+			}
+			r[Rd] = v;
+			nz(v);
+			break;
+		case 4:
+			io();
+			v = a;
+			if(b != 0){
+				if(b < 32){
+					c = v >> b - 1;
+					v >>= b;
+				}else
+					c = v = -((int)v < 0);
+				cpsr = cpsr & ~FLAGC | c << 29 & FLAGC;
+			}
+			r[Rd] = v;
+			nz(v);
+			break;
+		case 5:
+			c = cpsr >> 29 & 1;
+			r[Rd] = a + b + c;
+			addflags(a, b, c);
+			break;
+		case 6:
+			c = cpsr >> 29 & 1;
+			r[Rd] = a + ~b + c;
+			addflags(a, ~b, c);
+			break;
+		case 7:
+			io();
+			b &= 31;
+			r[Rd] = v = a >> b | a << 32 - b;
+			if(r[Rs] != 0){
+				c = a >> (b - 1 & 31);
+				cpsr = cpsr & ~FLAGC | c << 29 & FLAGC;
+			}
+			nz(v);
+			break;
+		case 8:
+			nz(a & b);
+			break;
+		case 9:
+			r[Rd] = -b;
+			addflags(0, ~b, 1);
+			break;
+		case 10:
+			addflags(a, ~b, 1);
+			break;
+		case 11:
+			addflags(a, b, 0);
+			break;
+		case 12:
+			r[Rd] = v = a | b;
+			nz(v);
+			break;
+		case 13:
+			r[Rd] = v = a * b;
+			mulspeed(a);
+			nz(v);
+			cpsr &= ~FLAGC;
+			break;
+		case 14:
+			r[Rd] = v = a & ~b;
+			nz(v);
+			break;
+		case 15:
+			r[Rd] = ~b;
+			nz(~b);
+			break;
+		}
+		break;
+	case 1:
+		Rd = instr & 7 | instr >> 4 & 8;
+		Rs = instr >> 3 & 15;
+		switch(instr >> 8 & 3){
+		case 0:
+			r[Rd] += r[Rs];
+			if(Rd == 15){
+				r[15] &= ~1;
+				pipeflush();
+			}
+			break;
+		case 1:
+			addflags(r[Rd], ~r[Rs], 1);
+			break;
+		case 2:
+			r[Rd] = r[Rs];
+			if(Rd == 15){
+				r[15] &= ~1;
+				pipeflush();
+			}
+			break;
+		case 3:
+			if((r[Rs] & 1) == 0)
+				setcpsr(cpsr & ~FLAGT);
+			r[15] = r[Rs] & ~1;
+			pipeflush();
+			break;
+		}
+		break;
+	case 2: case 3:
+		Rd = instr >> 8 & 7;
+		a = (r[15] & ~3) + (((u8int) instr) << 2);
+		io();
+		r[Rd] = memread(a & ~3, 4, 0);
+		break;
+	}
+}
+
+static void
+thldst(u16int instr)
+{
+	int Rd, Rb, Ro, size, sx, load, sh;
+	u32int v, off;
+	
+	Rd = instr & 7;
+	Rb = instr >> 3 & 7;
+	sx = 0;
+	switch(instr >> 13){
+	case 2:
+		Ro = instr >> 6 & 7;
+		off = r[Ro];
+		if((instr & 1<<9) != 0){
+			load = instr & 3<<10;
+			sx = instr & 1<<10;
+			size = load == 1<<10 ? 1 : 2;
+		}else{
+			load = instr & 1<<11;
+			size = (instr & 1<<10) != 0 ? 1 : 4;
+		}
+		break;
+	default:
+	case 3:
+		if((instr & 1<<12) != 0){
+			off = instr >> 6 & 0x1f;
+			size = 1;
+		}else{
+			off = instr >> 4 & 0x7c;
+			size = 4;
+		}
+		load = instr & 1<<11;
+		break;
+	case 4:
+		if((instr & 1<<12) == 0){
+			off = instr >> 5 & 0x3e;
+			size = 2;
+			load = instr & 1<<11;
+		}else{
+			Rb = 13;
+			Rd = instr >> 8 & 7;
+			off = instr << 2 & 0x3fc;
+			load = instr & 1<<11;
+			size = 4;
+		}
+		break;
+	}
+	off += r[Rb];
+	if(load){
+		io();
+		v = memread(off & ~(size - 1), size, 0);
+		if(sx)
+			if(size == 2)
+				v = ((int)(v << 16)) >> 16;
+			else
+				v = ((int)(v << 24)) >> 24;
+		if((off & size - 1) != 0){
+			sh = (off & size - 1) << 3;
+			v = v >> sh | v << 32 - sh;
+		}
+		r[Rd] = v;
+	}else
+		memwrite(off, r[Rd], size);
+}
+
+static void
+thldaddr(u16int instr)
+{
+	int Rd, imm, v;
+
+	imm = instr << 2 & 0x3fc;
+	if((instr & 1<<11) != 0)
+		v = r[13];
+	else
+		v = r[15] & ~3;
+	Rd = instr >> 8 & 7;
+	r[Rd] = v + imm;
+}
+
+static void
+thmulti(u16int instr)
+{
+	int off, lr, Rb;
+	int i, seq;
+	u32int addr;
+	
+	if((instr >> 8) == 0xb0){
+		off = instr << 2 & 0x1fc;
+		if((instr & 1<<7) != 0)
+			off = -off;
+		r[13] += off;
+		return;
+	}
+	if(instr >> 14 != 3){
+		Rb = 13;
+		lr = instr & 1<<8;
+	}else{
+		Rb = instr >> 8 & 7;
+		lr = 0;
+	}
+	addr = r[Rb];
+	seq = 0;
+	if((instr & 1<<11) != 0){
+		io();
+		for(i = 0; i < 8; i++){
+			if((instr & 1<<i) == 0)
+				continue;
+			r[i] = memread(addr & ~3, 4, seq);
+			addr += 4;
+			seq = 1;
+		}
+		if(lr){
+			r[15] = memread(addr & ~3, 4, seq) & ~1;
+			pipeflush();
+			addr += 4;
+		}
+	}else if(Rb == 13){
+		if(lr){
+			addr -= 4;
+			memwrite(addr & ~3, r[14], 4);
+		}
+		for(i = 7; i >= 0; i--){
+			if((instr & 1<<i) == 0)
+				continue;
+			addr -= 4;
+			memwrite(addr & ~3, r[i], 4);
+		}
+	}else
+		for(i = 0; i < 8; i++){
+			if((instr & 1<<i) == 0)
+				continue;
+			memwrite(addr & ~3, r[i], 4);
+			addr += 4;
+		}
+	if(Rb == 13 || (instr & 1<<Rb) == 0)
+		r[Rb] = addr;
+}
+
+static void
+thcondbranch(u16int instr)
+{
+	if((instr >> 8 & 15) == 0xf){
+		interrupt(MSVC);
+		return;
+	}
+	if(!cond(instr >> 8 & 15, instr))
+		return;
+	r[15] += ((int)(instr << 24)) >> 23;
+	pipeflush();
+}
+
+static void
+thbranch(u16int instr)
+{
+	r[15] += ((int)(instr << 21)) >> 20;
+	pipeflush();
+}
+
+static void
+thlbranch(u16int instr)
+{
+	if((instr & 1<<11) != 0){
+		r[15] = r[14] + (instr << 1 & 0xffe);
+		r[14] = curpc + 3;
+		pipeflush();
+	}else
+		r[14] = r[15] + ((int)(instr << 21)>>9);
+}
+
+static int
+stepthumb(void)
+{
+	u16int instr;
+	int s;
+
+	cyc = 0;
+	if((pipel & 2) != 0)
+		goto fetch;
+	if(irq && (cpsr & FLAGI) == 0){
+		interrupt(MIRQ);
+		return 1;
+	}
+	curpc = r[15] - 4;
+	instr = instr1;
+	if(trace)
+		print("T %.8ux %.4ux %.8ux %.8ux %.8ux | %.8ux %.8ux %.8ux %.8ux | %.8ux %.8ux %.8ux %.8ux\n", curpc, instr, cpsr, r[13], r[14], r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7]);
+	switch(instr >> 12 & 0xf){
+	case 0: case 1:
+		thshadd(instr);
+		break;
+	case 2: case 3:
+		thaddimm(instr);
+		break;
+	case 4:
+		thalu(instr);
+		break;
+	case 5: case 6: case 7: case 8: case 9:
+		thldst(instr);
+		break;
+	case 10:
+		thldaddr(instr);
+		break;
+	case 11: case 12:
+		thmulti(instr);
+		break;
+	case 13:
+		thcondbranch(instr);
+		break;
+	case 14:
+		thbranch(instr);
+		break;
+	case 15:
+		thlbranch(instr);
+		break;
+	default:
+		undefined(instr);
+	}
+fetch:
+	instr1 = instr0;
+	s = step == steparm ? 4 : 2;
+	instr0 = memread(r[15], s, pipel != -1);
+	r[15] += s;
+	pipel <<= 1;
+	return cyc;
+}
+
+void
+reset(void)
+{
+	setcpsr(0xd3);
+	r[15] = 0;
+	pipel = -1;
+}
--- /dev/null
+++ b/sys/src/games/gba/dat.h
@@ -1,0 +1,114 @@
+typedef char s8int;
+typedef short s16int;
+typedef long s32int;
+typedef vlong s64int;
+
+extern int cpuhalt, trace, keys;
+
+extern u32int curpc;
+extern int irq;
+
+extern int dmaact;
+extern uchar vram[];
+extern u16int pram[], oam[];
+extern u16int reg[];
+extern uchar *rom, *back;
+extern int nrom, nback, backup;
+
+extern int ppux, ppuy;
+extern u8int bldy, blda, bldb;
+
+extern int scale;
+
+enum {
+	DISPCNT = 0x0/2,
+	DISPSTAT = 0x4/2,
+	BG0CNT = 0x8/2,
+	BG0HOFS = 0x10/2,
+	BG0VOFS = 0x12/2,
+
+	BG2PA = 0x20/2,
+	BG2PB = 0x22/2,
+	BG2PC = 0x24/2,
+	BG2PD = 0x26/2,
+	BG2XL = 0x28/2,
+	BG2XH = 0x2a/2,
+	BG2YL = 0x2c/2,
+	BG2YH = 0x2e/2,
+	
+	WIN0H = 0x40/2,
+	WIN1H = 0x42/2,
+	WIN0V = 0x44/2,
+	WIN1V = 0x46/2,
+	WININ = 0x48/2,
+	WINOUT = 0x4a/2,
+	BLDCNT = 0x50/2,
+	BLDALPHA = 0x52/2,
+	BLDY = 0x54/2,
+	
+	DMA0CNTH = 0xba/2,
+	DMA1CNTH = 0xc6/2,
+	DMA2CNTH = 0xd2/2,
+	DMA3CNTH = 0xde/2,
+	
+	KEYCNT = 0x132/2,
+
+	IE = 0x200/2,
+	IF = 0x202/2,
+	WAITCNT = 0x204/2,
+	IME = 0x208/2,
+	
+};
+
+enum {
+	/* DISPCNT */
+	FRAME = 1<<4,
+	HBLFREE = 1<<5,
+	OBJNOMAT = 1<<6,
+	FBLANK = 1<<7,
+
+	/* DISPSTAT */
+	IRQVBLEN = 1<<3,
+	IRQHBLEN = 1<<4,
+	IRQVCTREN = 1<<5,
+
+	/* BGnCNT */
+	BG8 = 1<<7,
+	DISPWRAP = 1<<13,
+	
+	/* DMAnCNTH */
+	DMADCNT = 5,
+	DMASCNT = 7,
+	DMAREP = 1<<9,
+	DMAWIDE = 1<<10,
+	DMAWHEN = 12,
+	DMAIRQ = 1<<14,
+	DMAEN = 1<<15,
+
+	DMAINC = 0,
+	DMADEC = 1,
+	DMAFIX = 2,
+	DMAINCREL = 3,
+
+	DMANOW = 0,
+	DMAVBL = 1,
+	DMAHBL = 2,
+	DMASPEC = 3,
+	DMASOUND = 4,
+	DMAVIDEO = 5,
+	
+	IRQVBL = 1<<0,
+	IRQHBL = 1<<1,
+	IRQVCTR = 1<<2,
+	IRQTIM0 = 1<<3,
+	IRQDMA0 = 1<<8,
+	IRQKEY = 1<<12,
+	
+	NOBACK = 0,
+	SRAM = 1,
+	EEPROM = 2,
+	FLASH = 3,
+	
+	KB = 1024,
+	BACKTYPELEN = 64,
+};
--- /dev/null
+++ b/sys/src/games/gba/fns.h
@@ -1,0 +1,13 @@
+u32int memread(u32int, int, int);
+void memwrite(u32int, u32int, int);
+extern int (*step)(void);
+void reset(void);
+void memreset(void);
+void setif(u16int);
+void ppustep(void);
+void timerstep(int t);
+void flush(void);
+int dmastep(void);
+void dmastart(int);
+void flushback(void);
+void writeback(void);
--- /dev/null
+++ b/sys/src/games/gba/gba.c
@@ -1,0 +1,439 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include "dat.h"
+#include "fns.h"
+
+int cpuhalt;
+int scale, profile;
+Rectangle picr;
+Image *bg, *tmp;
+Mousectl *mc;
+int keys, paused, framestep, backup;
+QLock pauselock;
+int savefd, saveframes;
+
+char *biosfile = "/sys/games/lib/gbabios.bin";
+
+int ppuclock;
+
+void *
+emalloc(ulong sz)
+{
+	void *v;
+	
+	v = malloc(sz);
+	if(v == nil)
+		sysfatal("malloc: %r");
+	setmalloctag(v, getcallerpc(&sz));
+	return v;
+}
+
+void
+writeback(void)
+{
+	if(saveframes == 0)
+		saveframes = 15;
+}
+
+void
+flushback(void)
+{
+	if(savefd >= 0)
+		pwrite(savefd, back, nback, BACKTYPELEN);
+	saveframes = 0;
+}
+
+void
+loadbios(void)
+{
+	extern uchar bios[16384];
+
+	int fd;
+	
+	fd = open(biosfile, OREAD);
+	if(fd < 0)
+		sysfatal("open: %r");
+	readn(fd, bios, 16384);
+	close(fd);
+}
+
+int
+romtype(int *size)
+{
+	u32int *p, n, v;
+	union {char a[4]; u32int u;} s1 = {"EEPR"}, s2 = {"SRAM"}, s3 = {"FLAS"};
+	
+	p = (u32int *) rom;
+	n = nrom / 4;
+	do{
+		v = *p++;
+		if(v == s1.u && memcmp(p - 1, "EEPROM_V", 8) == 0){
+			print("backup type is either eeprom4 or eeprom64 -- can't detect which one\n");
+			return NOBACK;
+		}
+		if(v == s2.u && memcmp(p - 1, "SRAM_V", 6) == 0){
+			*size = 32*KB;
+			return SRAM;
+		}
+		if(v == s3.u){
+			if(memcmp(p - 1, "FLASH_V", 7) == 0 || memcmp(p - 1, "FLASH512_V", 10) == 0){
+				*size = 64*KB;
+				return FLASH;
+			}
+			if(memcmp(p - 1, "FLASH1M_V", 9) == 0){
+				*size = 128*KB;
+				return FLASH;
+			}
+		}
+	}while(--n);
+	return NOBACK;
+}
+
+int
+parsetype(char *s, int *size)
+{
+	if(strcmp(s, "eeprom4") == 0){
+		*size = 512;
+		return EEPROM;
+	}else if(strcmp(s, "eeprom64") == 0){
+		*size = 8*KB;
+		return EEPROM;
+	}else if(strcmp(s, "sram256") == 0){
+		*size = 32*KB;
+		return SRAM;
+	}else if(strcmp(s, "flash512") == 0){
+		*size = 64*KB;
+		return FLASH;
+	}else if(strcmp(s, "flash1024") == 0){
+		*size = 128*KB;
+		return FLASH;
+	}else
+		return NOBACK;
+}
+
+void
+typename(char *s, int type, int size)
+{
+	char *st;
+	switch(type){
+	case EEPROM:
+		st = "eeprom";
+		break;
+	case FLASH:
+		st = "flash";
+		break;
+	case SRAM:
+		st = "sram";
+		break;
+	default:
+		sysfatal("typestr: unknown type %d -- shouldn't happen", type);
+		return;
+	}
+	snprint(s, BACKTYPELEN, "%s%d", st, size/128);
+}
+
+void
+loadsave(char *file)
+{
+	char *buf, *p;
+	char tstr[BACKTYPELEN];
+	int type, size;
+	
+	buf = emalloc(strlen(file) + 4);
+	strcpy(buf, file);
+	p = strchr(buf, '.');
+	if(p == nil)
+		p = buf + strlen(buf);
+	strcpy(p, ".sav");
+	savefd = open(buf, ORDWR);
+	if(savefd < 0){
+		if(backup == NOBACK){
+			backup = romtype(&nback);
+			if(backup == NOBACK){
+				fprint(2, "failed to autodetect save format\n");
+				free(buf);
+				return;
+			}
+		}
+		savefd = create(buf, OWRITE, 0664);
+		if(savefd < 0){
+			fprint(2, "create: %r");
+			free(buf);
+			return;
+		}
+		memset(tstr, 0, sizeof(tstr));
+		typename(tstr, backup, nback);
+		write(savefd, tstr, sizeof(tstr));
+		back = emalloc(nback);
+		memset(back, 0, nback);
+		write(savefd, back, nback);
+		free(buf);
+		atexit(flushback);
+		return;
+	}
+	readn(savefd, tstr, sizeof(tstr));
+	tstr[31] = 0;
+	type = parsetype(tstr, &size);
+	if(type == NOBACK || backup != NOBACK && (type != backup || nback != size))
+		sysfatal("%s: invalid format", buf);
+	backup = type;
+	nback = size;
+	back = emalloc(nback);
+	readn(savefd, back, nback);
+	atexit(flushback);
+	free(buf);
+}
+
+void
+loadrom(char *file)
+{
+	int fd;
+	vlong sz;
+	
+	fd = open(file, OREAD);
+	if(fd < 0)
+		sysfatal("open: %r");
+	sz = seek(fd, 0, 2);
+	if(sz <= 0 || sz >= 32*1024*1024)
+		sysfatal("nope.jpg");
+	seek(fd, 0, 0);
+	nrom = sz;
+	rom = emalloc(nrom);
+	if(readn(fd, rom, sz) < sz)
+		sysfatal("read: %r");
+	close(fd);
+	loadsave(file);
+	if(nrom == 32*KB*KB && backup == EEPROM)
+		nrom -= 256;
+}
+
+void
+screeninit(void)
+{
+	Point p;
+
+	p = divpt(addpt(screen->r.min, screen->r.max), 2);
+	picr = (Rectangle){subpt(p, Pt(scale * 120, scale * 80)), addpt(p, Pt(scale * 120, scale * 80))};
+	tmp = allocimage(display, Rect(0, 0, scale * 240, scale > 1 ? 1 : scale * 160), CHAN4(CIgnore, 1, CBlue, 5, CGreen, 5, CRed, 5), scale > 1, 0);
+	bg = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xCCCCCCFF);
+	draw(screen, screen->r, bg, nil, ZP);	
+}
+
+void
+keyproc(void *)
+{
+	int fd, k;
+	static char buf[256];
+	char *s;
+	Rune r;
+
+	fd = open("/dev/kbd", OREAD);
+	if(fd < 0)
+		sysfatal("open: %r");
+	for(;;){
+		if(read(fd, buf, sizeof(buf) - 1) <= 0)
+			sysfatal("read /dev/kbd: %r");
+		if(buf[0] == 'c'){
+			/*if(utfrune(buf, KF|5))
+				savereq = 1;
+			if(utfrune(buf, KF|6))
+				loadreq = 1;*/
+			if(utfrune(buf, Kdel)){
+				close(fd);
+				threadexitsall(nil);
+			}
+			if(utfrune(buf, 't'))
+				trace = !trace;
+		}
+		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 'z': k |= 1<<1; break;
+			case 'x': k |= 1<<0; break;
+			case 'a': k |= 1<<9; break;
+			case 's': k |= 1<<8; break;
+			case Kshift: k |= 1<<2; break;
+			case 10: k |= 1<<3; break;
+			case Kup: k |= 1<<6; break;
+			case Kdown: k |= 1<<7; break;
+			case Kleft: k |= 1<<5; break;
+			case Kright: k |= 1<<4; break;
+			case Kesc:
+				if(paused)
+					qunlock(&pauselock);
+				else
+					qlock(&pauselock);
+				paused = !paused;
+				break;
+			case KF|1:	
+				if(paused){
+					qunlock(&pauselock);
+					paused=0;
+				}
+				framestep = !framestep;
+				break;
+			}
+		}
+		keys = k;
+	}
+
+}
+
+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, 200, 30), screen->r.min), bg, nil, ZP);
+	string(screen, addpt(screen->r.min, Pt(10, 10)), display->black, ZP, display->defaultfont, buf);
+	old = nsec();
+}
+
+void
+flush(void)
+{
+	extern uchar pic[];
+	Mouse m;
+	int x;
+
+	if(nbrecvul(mc->resizec) > 0){
+		if(getwindow(display, Refnone) < 0)
+			sysfatal("resize failed: %r");
+		screeninit();
+	}
+	while(nbrecv(mc->c, &m) > 0)
+		;
+	if(scale == 1){
+		loadimage(tmp, tmp->r, pic, 240*160*2);
+		draw(screen, picr, tmp, nil, ZP);
+	} else {
+		Rectangle r;
+		uchar *s;
+		int w;
+
+		s = pic;
+		r = picr;
+		w = 240*2*scale;
+		while(r.min.y < picr.max.y){
+			loadimage(tmp, tmp->r, s, w);
+			s += w;
+			r.max.y = r.min.y+scale;
+			draw(screen, r, tmp, nil, ZP);
+			r.min.y = r.max.y;
+		}
+	}
+	flushimage(display, 1);
+	if(profile)
+		timing();
+	if(framestep){
+		paused = 1;
+		qlock(&pauselock);
+		framestep = 0;
+	}
+	
+	if(saveframes > 0 && --saveframes == 0)
+		flushback();
+	
+	if((reg[KEYCNT] & 1<<14) != 0){
+		x = reg[KEYCNT] & keys;
+		if((reg[KEYCNT] & 1<<15) != 0){
+			if(x == (reg[KEYCNT] & 0x3ff))
+				setif(IRQKEY);
+		}else
+			if(x != 0)
+				setif(IRQKEY);
+	}
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-23T] [-s savetype] [-b biosfile] rom\n", argv0);
+	exits("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	char *s;
+	int t;
+
+	scale = 1;
+	ARGBEGIN {
+	case '2':
+		scale = 2;
+		break;
+	case '3':
+		scale = 3;
+		break;
+	case 's':
+		s = EARGF(usage());
+		backup = parsetype(s, &nback);
+		if(backup == NOBACK)
+			sysfatal("unknown save type '%s'", s);
+		break;
+	case 'b':
+		biosfile = strdup(EARGF(usage()));
+		break;
+	case 'T':
+		profile++;
+		break;
+	default:
+		usage();
+	} ARGEND;
+	if(argc < 1)
+		usage();
+
+	loadbios();
+	loadrom(argv[0]);
+	
+	if(initdraw(nil, nil, nil) < 0)
+		sysfatal("initdraw: %r");
+	mc = initmouse(nil, screen);
+	if(mc == nil)
+		sysfatal("initmouse: %r");
+	proccreate(keyproc, nil, mainstacksize);
+	screeninit();
+	
+	memreset();
+	reset();
+	for(;;){
+		if(paused){
+			qlock(&pauselock);
+			qunlock(&pauselock);
+		}
+		if(dmaact)
+			t = dmastep();
+		else if(cpuhalt)
+			t = 8;
+		else
+			t = step();
+		ppuclock += t;
+		while(ppuclock >= 4){
+			ppustep();
+			ppuclock -= 4;
+		}
+		timerstep(t);
+	}
+}
--- /dev/null
+++ b/sys/src/games/gba/mem.c
@@ -1,0 +1,665 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+uchar bios[16*KB], wram0[32*KB], wram1[256*KB];
+uchar vram[96*KB];
+u16int pram[512], oam[512];
+uchar *rom, *back;
+int nrom, nback;
+u16int reg[512];
+u16int tim[4];
+int timerclock;
+int dmaact;
+enum {
+	DMASRC,
+	DMADST,
+	DMACNT
+};
+u32int dmar[16];
+u8int waitst[16] = {5, 5, 5, 5, 3, 5, 5, 9, 8, 10, 10, 14};
+
+extern int cyc;
+
+static int eepromread(void);
+static void eepromwrite(int);
+static u8int flashread(u16int);
+static void flashwrite(u16int, u8int);
+
+static u32int
+arread(uchar *c, int n)
+{
+	switch(n){
+	default:
+		return c[0];
+	case 2:
+		return c[0] | c[1] << 8;
+	case 4:
+		return c[0] | c[1] << 8 | c[2] << 16 | c[3] << 24;
+	}
+}
+
+static void
+arwrite(uchar *c, u32int v, int n)
+{
+	switch(n){
+	case 4:
+		c[3] = v >> 24;
+		c[2] = v >> 16;
+	case 2:
+		c[1] = v >> 8;
+	default:
+		c[0] = v;
+	}
+}
+
+static u32int
+ar16read(u16int *c, int h, int n)
+{
+	switch(n){
+	case 1:
+		return c[0] >> (h << 3);
+	default:
+		return c[0];
+	case 4:
+		return c[0] | c[1] << 16;
+	}
+}
+
+static void
+ar16write(u16int *c, int h, u32int v, int n)
+{
+	switch(n){
+	case 1:
+		if(h)
+			c[0] = c[0] & 0xff | ((u8int)v) << 8;
+		else
+			c[0] = c[0] & 0xff00 | (u8int)v;
+		break;
+	case 2:
+		c[0] = v;
+		break;
+	case 4:
+		c[0] = v;
+		c[1] = v >> 16;
+		break;
+	}
+}
+
+static u32int
+regread(u32int a)
+{
+	u32int v;
+
+	switch(a){
+	case DISPSTAT*2:
+		v = reg[a/2] & ~7;
+		
+		if(ppuy >= 160 && ppuy != 227)
+			v |= 1;
+		if(ppux >= 240)
+			v |= 2;
+		if(ppuy == v >> 8)
+			v |= 4;
+		return v;
+	case 0x006:
+		return ppuy;
+	case 0x100: case 0x104: case 0x108: case 0x10c:
+		return tim[(a - 0x100) / 4];
+	case 0x130:
+		return keys ^ 0x3ff;
+	default:
+		return reg[a/2];
+	}
+}
+
+static void
+regwrite16(u32int a, u16int v)
+{
+	u16int *p;
+	int i;
+	static u8int ws0[4] = {5,4,3,9};
+	
+	p = &reg[a/2];
+	switch(a){
+	case IF*2:
+		*p &= ~v;
+		setif(0);
+		return;
+	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 DMA0CNTH*2: case DMA1CNTH*2: case DMA2CNTH*2: case DMA3CNTH*2:
+		if((*p & DMAEN) == 0 && (v & DMAEN) != 0){
+			i = (a - DMA0CNTH*2) / 12;
+			if((v >> DMAWHEN & 3) == 0)
+				dmaact |= 1<<i;
+			if(i == 3 && (v >> DMAWHEN & 3) == 3)
+				print("DMA video capture mode\n");
+			dmar[4*i + DMASRC] = p[-5] | p[-4] << 16;
+			dmar[4*i + DMADST] = p[-3] | p[-2] << 16;
+			dmar[4*i + DMACNT] = p[-1];
+		}
+		break;
+	case 0x102: case 0x106: case 0x10a: case 0x10e:
+		if((*p & 1<<7) == 0 && (v & 1<<7) != 0)
+			tim[(a-0x102)/4] = p[-1];
+		break;
+	case IME*2: case IE*2:
+		setif(0);
+		break;
+	case WAITCNT*2:
+		waitst[3] = waitst[7] = ws0[v & 3];
+		waitst[0] = ws0[v >> 2 & 3];
+		waitst[4] = ((v & 1<<4) == 0) + 2;
+		waitst[1] = ws0[v >> 5 & 3];
+		waitst[5] = (v & 1<<7) == 0 ? 5 : 2;
+		waitst[2] = ws0[v >> 8 & 3];
+		waitst[6] = (v & 1<<10) == 0 ? 9 : 2;
+		for(i = 0; i < 8; i++)
+			waitst[8 + i] = waitst[i] + waitst[i | 4];
+		break;
+	case 0x301:
+		cpuhalt = 1;
+		break;
+	}
+	*p = v;
+}
+
+static void
+regwrite(u32int a, u32int v, int n)
+{
+	u16int w;
+
+	switch(n){
+	case 1:
+		if((a & ~1) == IF)
+			w = 0;
+		else
+			w = regread(a);
+		if((a & 1) == 0)
+			w = w & 0xff00 | (u8int)v;
+		else
+			w = w & 0xff | v << 8;
+		regwrite16(a, w);
+		break;
+	default:
+		if((a & 1) != 0)
+			sysfatal("unaligned register access");
+		regwrite16(a, v);
+		break;
+	case 4:
+		if((a & 1) != 0)
+			sysfatal("unaligned register access");
+		regwrite16(a, v);
+		regwrite16(a + 2, v >> 16);
+		break;
+	}
+}
+
+void
+setif(u16int v)
+{
+	reg[IF] |= v;
+	irq = (reg[IME] & 1) != 0 && (reg[IF] & reg[IE]) != 0;
+	if(irq)
+		cpuhalt = 0;
+}
+
+u32int
+memread(u32int a, int n, int seq)
+{
+	u32int b;
+	assert((a & n-1) == 0);
+
+	switch(a >> 24){
+	case 0:
+		b = a & sizeof(bios) - 1;
+		cyc++;
+		return arread(bios + b, n);
+	case 2:
+		b = a & sizeof(wram1) - 1;
+		cyc += n > 2 ? 6 : 3;
+		return arread(wram1 + b, n);
+	case 3:
+		b = a & sizeof(wram0) - 1;
+		cyc++;
+		return arread(wram0 + b, n);
+	case 4:
+		b = a & 0xffffff;
+		if(b >= sizeof(reg)) goto fault;
+		cyc++;
+		if(n == 4)
+			return regread(b) | regread(b+2) << 16;
+		return regread(b);
+	case 5:
+		b = a & sizeof(pram) - 1;
+		cyc += (n+1) >> 1;
+		return ar16read(pram + b/2, b & 1, n);
+	case 6:
+		b = a & 128*KB - 1;
+		if(b >= 64*KB)
+			b &= ~(32*KB);
+		cyc += (n+1) >> 1;
+		return arread(vram + b, n);
+	case 7:
+		b = a & sizeof(oam) - 1;
+		cyc++;
+		return ar16read(oam + b/2, b & 1, n);
+	case 8: case 9: case 10: case 11: case 12: case 13:
+		b = a & 0x1ffffff;
+		cyc += waitst[(a >> 25) - 4 | seq << 2 | (n > 2) << 3];
+		if(b >= nrom){
+			if(backup == EEPROM && b >= 0x1000000 && (nrom < 16*KB*KB || b >= 0x1ffff00))
+				return eepromread();
+			return 0;
+		}
+		return arread(rom + b, n);
+	case 14:
+		if(backup == SRAM){
+			b = a & nback - 1;
+			return arread(back + b, n);
+		}
+		if(backup == FLASH)
+			return flashread(a);
+		return 0;
+	default:
+	fault:
+		sysfatal("read from %#.8ux (pc=%#.8ux)", a, curpc);
+		return 0;
+	}
+}
+
+void
+memwrite(u32int a, u32int v, int n)
+{
+	u32int b;
+	assert((a & n-1) == 0);
+
+	switch(a >> 24){
+	case 0:
+		return;
+	case 2:
+		b = a & sizeof(wram1) - 1;
+		cyc += n > 2 ? 6 : 3;
+		arwrite(wram1 + b, v, n);
+		return;
+	case 3:
+		b = a & sizeof(wram0) - 1;
+		cyc++;
+		arwrite(wram0 + b, v, n);
+		return;
+	case 4:
+		cyc++;
+		b = a & 0xffffff;
+		if(b == 0x410) return;
+		if(b >= sizeof(reg)) goto fault;
+		regwrite(b, v, n);
+		return;
+	case 5:
+		b = a & sizeof(pram) - 1;
+		cyc += (n+1) >> 1;
+		ar16write(pram + b/2, b & 1, v, n);
+		return;
+	case 6:
+		b = a & 128*KB - 1;
+		if(b >= 64*KB)
+			b &= ~(32*KB);
+		cyc += (n+1) >> 1;
+		arwrite(vram + b, v, n);
+		return;
+	case 7:
+		b = a & sizeof(oam) - 1;
+		cyc++;
+		ar16write(oam + b/2, b & 1, v, n);
+		return;
+	case 8: case 9: case 10: case 11: case 12: case 13:
+		if(backup == EEPROM){
+			b = a & 0x01ffffff;
+			if(b >= 0x1000000 && (nrom < 16*KB*KB || b >= 0x1ffff00))
+				eepromwrite(v & 1);
+		}
+		return;
+	case 14:
+		if(backup == SRAM){
+			b = a & nback - 1;
+			arwrite(back + b, v, n);
+			writeback();
+			return;
+		}
+		if(backup == FLASH){
+			flashwrite(a, v);
+			return;
+		}
+		return;
+	default:
+	fault:
+		sysfatal("write to %#.8ux, value %#.8ux (pc=%#.8ux)", a, v, curpc);
+	}
+}
+
+void
+memreset(void)
+{
+	reg[0x88/2] = 0x200;
+}
+
+void
+timerstep(int t)
+{
+	int i, carry;
+	u16int c;
+	u16int nt;
+
+	nt = -t;
+	carry = 0;
+	timerclock += t;
+	for(i = 0; i < 4; i++){
+		c = reg[0x102/2 + i*2];
+		if((c & 1<<7) == 0)
+			goto next;
+		if((c & 1<<2) == 0)
+			switch(c & 3){
+			case 1:
+				if((timerclock & 63) != 0)
+					goto next;
+				break;
+			case 2:
+				if((timerclock & 255) != 0)
+					goto next;
+				break;
+			case 3:
+				if((timerclock & 1023) != 0)
+					goto next;
+				break;
+			}
+		else
+			if(!carry)
+				goto next;
+		if(carry = tim[i] >= nt){
+			tim[i] += reg[0x100/2 + i*2];
+			if((c & 1<<6) != 0)
+				setif(IRQTIM0 << i);
+		}
+		tim[i] += t;
+		continue;
+	next:
+		carry = 0;
+	}
+}
+
+int
+dmastep(void)
+{
+	int i;
+	u16int *cntp, cnt;
+	u32int *dr;
+	u32int v;
+	int sz;
+	
+	cyc = 0;
+	for(i = 0; i < 4; i++)
+		if((dmaact & 1<<i) != 0)
+			break;
+	if(i == 4)
+		return cyc;
+	curpc = -1;
+	cntp = reg + DMA0CNTH + i * 6;
+	cnt = *cntp;
+	dr = dmar + 4 * i;
+
+	sz = (cnt & DMAWIDE) != 0 ? 4 : 2;
+	if(i == 0)
+		dr[DMASRC] &= 0x07FFFFFF;
+	else
+		dr[DMASRC] &= 0x0FFFFFFF;
+	if(i != 3)
+		dr[DMADST] &= 0x7FFFFFFF;
+	else
+		dr[DMADST] &= 0x0FFFFFFF;
+	v = memread(dr[DMASRC] & -sz, sz, 1);
+	memwrite(dr[DMADST] & -sz, v, sz);
+	switch(cnt >> DMADCNT & 3){
+	case DMAINC: case DMAINCREL: dr[DMADST] += sz; break;
+	case DMADEC: dr[DMADST] -= sz; break;
+	}
+	switch(cnt >> DMASCNT & 3){
+	case DMAINC: dr[DMASRC] += sz; break;
+	case DMADEC: dr[DMASRC] -= sz; break;
+	}
+	if(dr[DMACNT] == 0)
+		dr[DMACNT] = i != 3 ? 0x4000 : 0x10000;
+	if(--dr[DMACNT] == 0){
+		dmaact &= ~(1<<i);
+		if((cnt & DMAREP) != 0){
+			dmar[DMACNT] = cntp[-1];
+			if((cnt >> DMADCNT & 3) == DMAINCREL)
+				dmar[DMADST] = cntp[-3] | cntp[-2] << 16;
+		}else
+			*cntp &= ~DMAEN;
+		if((cnt & DMAIRQ) != 0)
+			setif(IRQDMA0 << i);
+	}
+	return cyc;
+}
+
+void
+dmastart(int cond)
+{
+	int i;
+	u16int *cntp, cnt, c;
+	
+	cntp = reg + DMA0CNTH;
+	for(i = 0; i < 3; i++, cntp += 6){
+		cnt = *cntp;
+		if((cnt & DMAEN) == 0)
+			continue;
+		c = cnt >> DMAWHEN & 3;
+		if(c == 3)
+			c += (i + 1) / 2;
+		if(c == cond)
+			dmaact |= 1<<i;
+	}
+}
+
+int eepromstate, eeprompos, eepromaddr;
+u64int eepromdata;
+
+enum {
+	EEPROMCMD,
+	EEPROMRDCMD,
+	EEPROMRDRESP,
+	EEPROMWRCMD,
+	EEPROMWRDATA,
+	EEPROMWRRESP,
+};
+
+static int
+eepromread(void)
+{
+	int v;
+
+	switch(eepromstate){
+	case EEPROMRDRESP:
+		eeprompos++;
+		if(eeprompos <= 4)
+			return 0;
+		if(eeprompos == 68){
+			eepromstate = EEPROMCMD;
+			eeprompos = 0;
+		}
+		v = eepromdata >> 63;
+		eepromdata <<= 1;
+		return v;
+	case EEPROMWRRESP:
+		if(++eeprompos == 1000){
+			eepromstate = EEPROMCMD;
+			eeprompos = 0;
+			return 1;
+		}
+		return 0;
+	default:
+		return 0;
+	}
+}
+
+static void
+eepromwrite(int n)
+{
+	uchar *p;
+
+	switch(eepromstate){
+	case EEPROMCMD:
+		eepromaddr = eepromaddr << 1 | n;
+		if(++eeprompos >= 2){
+			switch(eepromaddr & 3){
+			case 2:
+				eepromstate = EEPROMWRCMD;
+				break;
+			case 3:
+				eepromstate = EEPROMRDCMD;
+				break;
+			}
+			eeprompos = 0;
+		}
+		break;
+	case EEPROMRDCMD:
+	case EEPROMWRCMD:
+		eepromaddr = eepromaddr << 1 | n;
+		eeprompos++;
+		if(nback == 512){
+			if(eeprompos >= 7)
+				eepromaddr = eepromaddr >> 1 & 0x3f;
+			else
+				break;
+		}else{
+			if(eeprompos >= 15)
+				eepromaddr = eepromaddr >> 1 & 0x3fff;
+			else
+				break;
+		}
+		if(eepromstate == EEPROMRDCMD){
+			p = back + eepromaddr * 8;
+			eepromdata = p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24 | (u64int)p[4] << 32 | 
+				(u64int)p[5] << 40 | (u64int)p[6] << 48 | (u64int)p[7] << 56;
+			eeprompos = 0;
+			eepromstate = EEPROMRDRESP;
+			break;
+		}else{
+			eepromdata = n;
+			eeprompos = 1;
+			eepromstate = EEPROMWRDATA;
+			break;
+		}
+	case EEPROMWRDATA:
+		if(eeprompos == 64){
+			p = back + eepromaddr * 8;
+			p[0] = eepromdata;
+			p[1] = eepromdata >> 8;
+			p[2] = eepromdata >> 16;
+			p[3] = eepromdata >> 24;
+			p[4] = eepromdata >> 32;
+			p[5] = eepromdata >> 40;
+			p[6] = eepromdata >> 48;
+			p[7] = eepromdata >> 56;
+			eepromstate = EEPROMWRRESP;
+			eeprompos = 0;
+			writeback();
+			break;
+		}
+		eepromdata = eepromdata << 1 | n;
+		eeprompos++;
+		break;
+	}
+}
+
+int flashstate, flashmode, flashbank;
+
+enum {
+	FLASHCMD0,
+	FLASHCMD1,
+	FLASHCMD2,
+	FLASHBANK,
+	FLASHWRITE,
+	
+	FLASHID = 1,
+	FLASHERASE = 2,
+};
+
+static u8int
+flashread(u16int a)
+{
+	if((flashmode & FLASHID) != 0)
+		return (a & 1) != 0 ? 0xd4 : 0xbf;
+	return back[(flashbank << 16) + a];
+}
+
+static void
+flashwrite(u16int a, u8int v)
+{
+	int erase;
+
+	switch(flashstate){
+	case FLASHCMD0:
+		if(a == 0x5555 && v == 0xaa)
+			flashstate = FLASHCMD1;
+		break;
+	case FLASHCMD1:
+		if(a == 0x2aaa && v == 0x55)
+			flashstate = FLASHCMD2;
+		else
+			flashstate = FLASHCMD0;
+		break;
+	case FLASHCMD2:
+		flashstate = FLASHCMD0;
+		erase = flashmode & FLASHERASE;
+		flashmode &= ~FLASHERASE;
+		switch(v){
+		case 0x90: flashmode |= FLASHID; break;
+		case 0xF0: flashmode &= ~FLASHID; break;
+		case 0x80: flashmode |= FLASHERASE; break;
+		case 0x10:
+			if(erase){
+				memset(back, 0xff, nback);
+				writeback();
+			}
+			break;
+		case 0x30:
+			if(erase){
+				memset(back + (a & 0xf000) + (flashbank << 16), 0xff, 4096);
+				writeback();
+			}
+			break;
+		case 0xA0:
+			writeback();
+			flashstate = FLASHWRITE;
+			break;
+		case 0xB0: flashstate = FLASHBANK; break;
+		default:
+			print("unknown flash cmd %x\n", v);
+		}
+		break;
+	case FLASHBANK:
+		flashbank = v % (nback >> 16);
+		flashstate = FLASHCMD0;
+		break;
+	case FLASHWRITE:
+		back[(flashbank << 16) + a] &= v;
+		writeback();
+		flashstate = FLASHCMD0;
+		break;
+	}
+}
+
--- /dev/null
+++ b/sys/src/games/gba/mkfile
@@ -1,0 +1,13 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin/games
+TARG=gba
+OFILES=\
+	cpu.$O\
+	mem.$O\
+	gba.$O\
+	ppu.$O\
+
+HFILES=dat.h fns.h
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/sys/src/games/gba/ppu.c
@@ -1,0 +1,653 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+int ppux, ppuy;
+uchar pic[240*160*2*3*3];
+u8int bldy, blda, bldb;
+
+typedef struct bg bg;
+struct bg {
+	uchar n;
+	uchar depth;
+
+	s32int rpx0, rpy0, rpx, rpy;
+	s32int sx, sy;
+	
+	u16int tx, ty;
+	u8int tnx, tny;
+	u16int t;
+	u8int *chr;
+	u16int *pal;
+};
+static u8int mode=-1;
+static bg bgst[4] = {{.n = 0}, {.n = 1}, {.n = 2}, {.n = 3}};
+static u32int pixeldat[2], pixelpri[2];
+static u16int bgmask;
+static u8int objwin, objtrans;
+
+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;
+};
+static sprite sprt[128], *sp = sprt;
+enum {
+	SPRROT = 1<<8,
+	SPRDIS = 1<<9,
+	SPRDOUB = 1<<9,
+	SPR8 = 1<<13,
+	SPRWIDE = 1<<14,
+	SPRTALL = 1<<15,
+	SPRHFLIP = 1<<28,
+	SPRVFLIP = 1<<29,
+	SPRSIZE0 = 1<<30,
+	SPRSIZE1 = 1<<31
+};
+
+void
+pixeldraw(int x, int y, u16int v)
+{
+	uchar *p;
+	u16int *q;
+	union { u16int w; u8int b[2]; } u;
+
+	if(scale == 1){
+		p = pic + (x + y * 240) * 2;
+		p[0] = v;
+		p[1] = v >> 8;
+		return;
+	}
+	u.b[0] = v;
+	u.b[1] = v >> 8;
+	if(scale == 2){
+		q = (u16int*)pic + (x + y * 240) * 2;
+		q[0] = u.w;
+		q[1] = u.w;
+	}else{
+		q = (u16int*)pic + (x + y * 240) * 3;
+		q[0] = u.w;
+		q[1] = u.w;
+		q[2] = u.w;
+	}
+}
+
+void
+pixel(u16int c, int n, int p)
+{
+	if(p < pixelpri[0]){
+		pixeldat[1] = pixeldat[0];
+		pixelpri[1] = pixelpri[0];
+		pixelpri[0] = p;
+		pixeldat[0] = c | n << 16;
+	}else if(p < pixelpri[1]){
+		pixelpri[1] = p;
+		pixeldat[1] = c | n << 16;
+	}
+}
+
+void
+tile(bg *b)
+{
+	u16int bgcnt, ta, tx, ty, y, t;
+	u8int d;
+	u8int *chr;
+	
+	bgcnt = reg[BG0CNT + b->n];
+	d = bgcnt >> 7 & 1;
+	tx = b->tx;
+	ty = b->ty;
+	ta = (bgcnt << 3 & 0xf800) + ((tx & 0x1f) << 1) + ((ty & 0x1f) << 6);
+	switch(bgcnt >> 14){
+	case 1: ta += tx << 6 & 0x800; break;
+	case 2: ta += ty << 6 & 0x800; break;
+	case 3: ta += tx << 6 & 0x800 | ty << 7 & 0x1000; break;
+	}
+	t = vram[ta] | vram[ta+1] << 8;
+	b->t = t;
+	chr = vram + (bgcnt << 12 & 0xc000) + ((t & 0x3ff) << 5+d);
+	y = b->tny;
+	if((t & 1<<11) != 0)
+		y ^= 7;
+	chr = chr + (y << 2+d);
+	b->chr = chr;
+	if(d != 0)
+		b->pal = pram;
+	else
+		b->pal = pram + (t >> 8 & 0xf0);
+}
+
+void
+bginit(bg *b, int scal, int bit)
+{
+	u16int cnt, x, y;
+	u16int *rr;
+	
+	cnt = reg[DISPCNT];
+	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;
+		}
+		b->rpx = b->rpx0;
+		b->rpy = b->rpy0;
+		b->rpx0 += (s16int)rr[BG2PB];
+		b->rpy0 += (s16int)rr[BG2PD];
+		switch(cnt & 7){
+		case 3:
+		case 4:
+			b->sx = 240 << 8;
+			b->sy = 160 << 8;
+			b->depth = (cnt & 7) == 3;
+			break;
+		case 5:
+			b->sx = 160 << 8;
+			b->sy = 128 << 8;
+			b->depth = 1;
+			break;
+		}
+	}else{
+		rr = reg + (b->n << 1);
+		x = rr[BG0HOFS] & 0x1ff;
+		y = (rr[BG0VOFS] & 0x1ff) + ppuy;
+		b->tx = x >> 3;
+		b->ty = y >> 3;
+		b->tnx = x & 7;
+		b->tny = y & 7;
+		tile(b);
+	}
+}
+
+void
+bgsinit(void)
+{
+	mode = reg[DISPCNT] & 7;
+	switch(mode){
+	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)
+{
+	u16int cnt;
+	int v;
+	uchar *p;
+	u16int *rr;
+	uchar *base;
+	
+	cnt = reg[DISPCNT];
+	rr = reg - 8 + (b->n << 3);
+	if((bgmask & 1<<b->n) == 0)
+		goto next;
+	if(b->rpx >= 0 && b->rpy >= 0 && b->rpx <= b->sx && b->rpy <= b->sy){
+		base = vram;
+		if((cnt & FRAME) != 0 && (cnt & 7) != 3)
+			base += 0xa000;
+		if(b->depth){
+			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;
+		}
+	}else
+		v = -1;
+	if(v >= 0)
+		pixel(v, b->n, reg[BG0CNT + b->n] & 3);
+next:
+	b->rpx += (s16int) rr[BG2PA];
+	b->rpy += (s16int) rr[BG2PC];
+}
+
+void
+rotbg(bg *b)
+{
+	u16int *rr, ta;
+	u16int bgcnt;
+	int row, sz, x, y;
+	uchar *p, v;
+
+	rr = reg - 8 + (b->n << 3);
+	if((bgmask & 1<<b->n) == 0)
+		goto next;
+	bgcnt = reg[BG0CNT + b->n];
+	row = (bgcnt >> 14) + 4;
+	sz = 1 << 3 + row;
+	x = b->rpx >> 8;
+	y = b->rpy >> 8;
+	if((bgcnt & DISPWRAP) != 0){
+		x &= sz - 1;
+		y &= sz - 1;
+	}else if((uint)x >= sz || (uint)y >= sz)
+		goto next;
+	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)
+		pixel(pram[v], b->n, bgcnt & 3);
+next:
+	b->rpx += (s16int) rr[BG2PA];
+	b->rpy += (s16int) rr[BG2PC];
+}
+
+void
+txtbg(bg *b)
+{
+	u16int bgcnt;
+	u8int x, v;
+
+	bgcnt = reg[BG0CNT + b->n];
+	if((bgmask & 1<<b->n) == 0)
+		goto next;
+	x = b->tnx;
+	if((b->t & 1<<10) != 0)
+		x ^= 7;
+	if((bgcnt & BG8) != 0)
+		v = b->chr[x];
+	else{
+		v = b->chr[x>>1];
+		if((x & 1) != 0)
+			v >>= 4;
+		else
+			v &= 0xf;
+	}
+	if(v != 0)
+		pixel(b->pal[v], b->n, bgcnt & 3);
+next:
+	if(++b->tnx == 8){
+		b->tnx = 0;
+		b->tx++;
+		tile(b);
+	}
+}
+
+void
+bgs(void)
+{
+	switch(mode){
+	case 0:
+		txtbg(&bgst[0]);
+		txtbg(&bgst[1]);
+		txtbg(&bgst[2]);
+		txtbg(&bgst[3]);
+		break;
+	case 1:
+		txtbg(&bgst[0]);
+		txtbg(&bgst[1]);
+		rotbg(&bgst[2]);
+		break;
+	case 2:
+		rotbg(&bgst[2]);
+		rotbg(&bgst[3]);
+		break;
+	case 3:
+	case 4:
+	case 5:
+		bitbg(&bgst[2]);
+		break;
+	}
+}
+
+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;
+		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(void)
+{
+	sprite *s;
+	ushort dx;
+	u32int t0;
+	uchar v;
+	ushort x, y;
+	u16int c;
+	int pv, ppri, pri;
+	uchar d;
+	uchar *b;
+	
+	pv = -1;
+	ppri = 6;;
+	for(s = sprt; s < sp; s++){
+		dx = ppux - s->x;
+		if(dx >= s->wb)
+			continue;
+		t0 = s->t0;
+		if((t0 & SPRROT) != 0){
+			x = s->rx >> 8;
+			y = s->ry >> 8;
+			if(x < s->w && y < s->h){
+				b = s->base;
+				d = (t0 & SPR8) != 0;
+				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;
+			s->rx += s->dx;
+			s->ry += s->dy;
+		}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(v != 0){
+			pri = s->t1 >> 10 & 3;
+			c = s->pal[v];
+			switch(s->t0 >> 10 & 3){
+			case 1:
+				c |= 1<<16;
+			case 0:
+				if(ppri > pri){
+					pv = c;
+					ppri = pri;
+				}
+				break;
+			case 2:
+				objwin = 1;
+				break;
+			}
+		}
+	}
+	if(pv >= 0){
+		pixel(pv, 4, ppri);
+		if(pv >> 16 != 0)
+			objtrans = 1;
+	}
+}
+
+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
+windows(void)
+{
+	u16int dispcnt;
+	u16int v, h;
+
+	dispcnt = reg[DISPCNT];
+	bgmask = dispcnt >> 8 | 1<<5;
+	if((dispcnt >> 13) != 0){
+		if((dispcnt & 1<<13) != 0){
+			v = reg[WIN0V];
+			h = reg[WIN0H];
+			if(ppuy < (u8int)v && ppuy >= v >> 8 &&
+				ppux < (u8int)h && ppux >= h >> 8){
+				bgmask &= reg[WININ];
+				goto windone;
+			}
+		}
+		if((dispcnt & 1<<14) != 0){
+			v = reg[WIN1V];
+			h = reg[WIN1H];
+			if(ppuy < (u8int)v && ppuy >= v >> 8 &&
+				ppux < (u8int)h && ppux >= h >> 8){
+				bgmask &= reg[WININ] >> 8;
+				goto windone;
+			}
+		}
+		if((dispcnt & 1<<15) != 0 && objwin != 0){
+			bgmask &= reg[WINOUT] >> 8;
+			goto windone;
+		}
+		bgmask &= reg[WINOUT];
+	}
+windone:
+	if(pixelpri[0] != 8 && (bgmask & 1<<4) == 0){
+		pixelpri[0] = 8;
+		pixeldat[0] = pram[0] | 5 << 16;
+	}
+}
+
+void
+colormath(void)
+{
+	u8int src0;
+	u16int bldcnt;
+	
+	if((bgmask & 1<<5) == 0)
+		return;
+	bldcnt = reg[BLDCNT];
+	src0 = pixeldat[0] >> 16;
+	if(objtrans && src0 == 4)
+		goto alpha;
+	if((bldcnt & 3<<6) == 0 || (bldcnt & 1<<src0) == 0)
+		return;
+	switch(bldcnt >> 6 & 3){
+	case 1:
+	alpha:
+		if((bldcnt & 1<<8+(pixeldat[1]>>16)) == 0)
+			return;
+		pixeldat[0] = mix(pixeldat[0], pixeldat[1]);
+		break;
+	case 2:
+		pixeldat[0] = brighten(pixeldat[0]);
+		break;
+	case 3:
+		pixeldat[0] = darken(pixeldat[0]);
+		break;
+	}
+}
+
+void
+ppustep(void)
+{
+	u16int stat;
+	u16int cnt;
+	
+	stat = reg[DISPSTAT];
+	cnt = reg[DISPCNT];
+	if(ppuy < 160 && ppux < 240)
+		if((cnt & FBLANK) == 0){
+			objwin = 0;
+			objtrans = 0;
+			pixelpri[0] = 8;
+			pixeldat[0] = pram[0] | 5 << 16;
+			if((cnt & 1<<12) != 0)
+				spr();
+			windows();
+			bgs();
+			colormath();
+			pixeldraw(ppux, ppuy, pixeldat[0]);
+		}else
+			pixeldraw(ppux, ppuy, 0xffff);
+	if(ppux == 240 && ppuy < 160){
+		if((stat & IRQHBLEN) != 0)
+			setif(IRQHBL);
+		dmastart(DMAHBL);
+	}
+	if(++ppux >= 308){
+		ppux = 0;
+		if(++ppuy >= 228){
+			ppuy = 0;
+			flush();
+		}
+		if((stat & IRQVCTREN) != 0 && ppuy == stat >> 8)
+			setif(IRQVCTR);
+		if(ppuy < 160){
+			bgsinit();
+			sprinit();
+		}else if(ppuy == 160){
+			if((stat & IRQVBLEN) != 0)
+				setif(IRQVBL);
+			dmastart(DMAVBL);
+		}
+	}
+}
--- /dev/null
+++ b/sys/src/games/gba/test.c
@@ -1,0 +1,15 @@
+#include <u.h>
+#include <libc.h>
+
+void main()
+{
+	int fd;
+	Dir *d;
+	int n, i;
+	
+	fd = open(".", OREAD);
+	n = dirreadall(fd, &d);
+	for(i = 0; i < n; i++)
+		if(d[i].name[0] == '\xef')
+			remove(d[i].name);
+}
\ No newline at end of file