#include #include #include #include #include #include #include "dat.h" #include "fns.h" extern uchar ppuram[16384]; int nprg, nchr, map, chrram; uchar *prg, *chr; int scale; Rectangle picr; Image *tmp, *bg; int clock, ppuclock, apuclock, dmcclock, dmcfreq, sampclock, msgclock, saveclock; Mousectl *mc; int keys, keys2, paused, savereq, loadreq, oflag, savefd = -1; int mirr; QLock pauselock; void message(char *fmt, ...) { va_list va; static char buf[512]; va_start(va, fmt); vsnprint(buf, sizeof buf, fmt, va); string(screen, Pt(10, 10), display->black, ZP, display->defaultfont, buf); msgclock = FREQ; va_end(va); } void flushram(void) { if(savefd >= 0) pwrite(savefd, mem + 0x6000, 0x2000, 0); saveclock = 0; } void loadrom(char *file, int sflag) { int fd; int nes20; char *s; static uchar header[16]; static u32int flags; static char buf[512]; fd = open(file, OREAD); if(fd < 0) sysfatal("open: %r"); if(readn(fd, header, sizeof(header)) < sizeof(header)) sysfatal("read: %r"); if(memcmp(header, "NES\x1a", 4) != 0) sysfatal("not a ROM"); if(header[15] != 0) memset(header + 7, 0, 9); flags = header[6] | header[7] << 8; nes20 = (flags & FLNES20M) == FLNES20V; if(flags & (FLVS | FLPC10)) sysfatal("ROM not supported"); nprg = header[HPRG]; if(nes20) nprg |= (header[HROMH] & 0xf) << 8; if(nprg == 0) sysfatal("invalid ROM"); nchr = header[HCHR]; if(nes20) nchr |= (header[HROMH] & 0xf0) << 4; map = (flags >> FLMAPPERL) & 0x0f | (((flags >> FLMAPPERH) & 0x0f) << 4); if(nes20) map |= (header[8] & 0x0f) << 8; if(map >= 256 || mapper[map] == nil) sysfatal("unimplemented mapper %d", map); memset(mem, 0, sizeof(mem)); if((flags & FLTRAINER) != 0 && readn(fd, mem + 0x7000, 512) < 512) sysfatal("read: %r"); prg = malloc(nprg * PRGSZ); if(prg == nil) sysfatal("malloc: %r"); if(readn(fd, prg, nprg * PRGSZ) < nprg * PRGSZ) sysfatal("read: %r"); chrram = nchr == 0; if(nchr != 0){ chr = malloc(nchr * CHRSZ); if(chr == nil) sysfatal("malloc: %r"); if(readn(fd, chr, nchr * CHRSZ) < nchr * CHRSZ) sysfatal("read: %r"); }else{ nchr = 1; chr = malloc(nchr * CHRSZ); if(chr == nil) sysfatal("malloc: %r"); } if((flags & FLFOUR) != 0) mirr = MFOUR; else if((flags & FLMIRROR) != 0) mirr = MVERT; else mirr = MHORZ; if(sflag){ strncpy(buf, file, sizeof buf - 5); s = buf + strlen(buf) - 4; if(s < buf || strcmp(s, ".nes") != 0) s += 4; strcpy(s, ".sav"); savefd = create(buf, ORDWR | OEXCL, 0666); if(savefd < 0) savefd = open(buf, ORDWR); if(savefd < 0) message("open: %r"); else readn(savefd, mem + 0x6000, 0x2000); atexit(flushram); } mapper[map](INIT, 0); } extern int trace; void joyproc(void *) { char *s, *down[9]; static char buf[64]; int n, k, j; 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; else if(strcmp(s, "a") == 0) k |= 1<<0; else if(strcmp(s, "b") == 0) k |= 1<<1; else if(strcmp(s, "control") == 0) k |= 1<<2; else if(strcmp(s, "start") == 0) k |= 1<<3; else if(strcmp(s, "up") == 0) k |= 1<<4; else if(strcmp(s, "down") == 0) k |= 1<<5; else if(strcmp(s, "left") == 0) k |= 1<<6; else if(strcmp(s, "right") == 0) k |= 1<<7; } if(j == 2) keys2 = k; else keys = k; } } 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, Kdel)){ close(fd); threadexitsall(nil); } if(utfrune(buf, KF|5)) savereq = 1; if(utfrune(buf, KF|6)) loadreq = 1; if(utfrune(buf, 't')) trace ^= 1; } 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 'x': k |= 1<<0; break; case 'z': k |= 1<<1; break; case Kshift: k |= 1<<2; break; case 10: k |= 1<<3; break; case Kup: k |= 1<<4; break; case Kdown: k |= 1<<5; break; case Kleft: k |= 1<<6; break; case Kright: k |= 1<<7; break; case Kesc: if(paused) qunlock(&pauselock); else qlock(&pauselock); paused = !paused; break; } } keys = k; } } void threadmain(int argc, char **argv) { int t, h, sflag; Point p; scale = 1; h = 240; sflag = 0; ARGBEGIN { case 'a': initaudio(); break; case '2': scale = 2; break; case '3': scale = 3; break; case 'o': oflag = 1; h -= 16; break; case 's': sflag = 1; break; default: goto usage; } ARGEND; if(argc != 1){ usage: fprint(2, "usage: %s [-23aos] rom\n", argv0); threadexitsall("usage"); } loadrom(argv[0], sflag); if(initdraw(nil, nil, nil) < 0) sysfatal("initdraw: %r"); mc = initmouse(nil, screen); if(mc == nil) sysfatal("initmouse: %r"); proccreate(joyproc, nil, 8192); proccreate(keyproc, nil, 8192); originwindow(screen, Pt(0, 0), screen->r.min); p = divpt(addpt(screen->r.min, screen->r.max), 2); picr = (Rectangle){subpt(p, Pt(scale * 128, scale * h/2)), addpt(p, Pt(scale * 128, scale * h/2))}; tmp = allocimage(display, Rect(0, 0, scale * 256, scale * h), XRGB32, 0, 0); bg = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xCCCCCCFF); draw(screen, screen->r, bg, nil, ZP); pc = memread(0xFFFC) | memread(0xFFFD) << 8; rP = FLAGI; dmcfreq = 12 * 428; for(;;){ if(savereq){ savestate("nes.save"); savereq = 0; } if(loadreq){ loadstate("nes.save"); loadreq = 0; } if(paused){ qlock(&pauselock); qunlock(&pauselock); } t = step() * 12; clock += t; ppuclock += t; apuclock += t; sampclock += t; dmcclock += t; while(ppuclock >= 4){ ppustep(); ppuclock -= 4; } if(apuclock >= APUDIV){ apustep(); apuclock -= APUDIV; } if(sampclock >= SAMPDIV){ audiosample(); sampclock -= SAMPDIV; } if(dmcclock >= dmcfreq){ dmcstep(); dmcclock -= dmcfreq; } if(msgclock > 0){ msgclock -= t; if(msgclock <= 0){ draw(screen, screen->r, bg, nil, ZP); msgclock = 0; } } if(saveclock > 0){ saveclock -= t; if(saveclock <= 0) flushram(); } } }