shithub: riscv

ref: b7a4dab54ab89f9cd62f3a31166cff553450cd28
dir: /sys/src/games/gba/gba.c/

View raw version
#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;
int clock;
int savereq, loadreq;

char *biosfile = "/sys/games/lib/gbabios.bin";

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 = strrchr(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("invalid file size");
	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))};
	freeimage(tmp);
	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);
	draw(screen, screen->r, bg, nil, ZP);	
}

void
keyproc(void *)
{
	int fd, n, k;
	static char buf[256];
	char *s;
	Rune r;

	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, 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;
			}
		}
		k &= ~(k << 1 & 0xa0 | k >> 1 & 0x50);
		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;
	static vlong old, delta;
	vlong new, diff;

	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(audioout() < 0){
		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;
	}
	
	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 [-23aT] [-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 'a':
		audioinit();
		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);
	bg = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xCCCCCCFF);
	screeninit();

	eventinit();
	memreset();
	reset();
	for(;;){
		if(savereq){
			savestate("gba.save");
			savereq = 0;
		}
		if(loadreq){
			loadstate("gba.save");
			loadreq = 0;
		}
		if(paused){
			qlock(&pauselock);
			qunlock(&pauselock);
		}
		if(dmaact)
			t = dmastep();
		else if(cpuhalt)
			t = 8;
		else
			t = step();
		clock += t;
		if((elist->time -= t) <= 0)
			popevent();
	}
}