ref: 86bfd0331d842c7210a3a4758b71231b18193b5b
dir: /sys/src/games/gb/gb.c/
#include <u.h> #include <libc.h> #include <draw.h> #include <thread.h> #include <mouse.h> #include <cursor.h> #include <keyboard.h> #include "dat.h" #include "fns.h" uchar *cart, *ram; int mbc, rombanks, rambanks, clock, ppuclock, divclock, timerclock, audioclock, msgclock, timerfreq, timer, keys, savefd, savereq, loadreq, scale, paused; Rectangle picr; Image *bg, *tmp; Mousectl *mc; QLock pauselock; void message(char *fmt, ...) { va_list va; 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 = CPUFREQ; va_end(va); } void loadrom(char *file) { int fd, i; vlong len; u8int ck; char buf[512]; char title[17]; Point p; char *s; extern int battery, ramen; fd = open(file, OREAD); if(fd < 0) sysfatal("open: %r"); len = seek(fd, 0, 2); if(len < 0) sysfatal("seek: %r"); if(len == 0 || len > 16*1048576) sysfatal("are you sure this is a ROM?"); cart = malloc(len); if(cart == nil) sysfatal("malloc: %r"); seek(fd, 0, 0); if(readn(fd, cart, len) < len) sysfatal("read: %r"); close(fd); ck = 0; for(i = 0x134; i <= 0x14C; i++) ck -= cart[i] + 1; if(ck != cart[0x14D]) sysfatal("checksum mismatch: %.2x != %.2x", ck, cart[0x14D]); memcpy(mem, cart, 32768); memset(title, 0, sizeof(title)); memcpy(title, cart+0x134, 16); battery = 0; switch(cart[0x147]){ case 0x09: battery = 1; case 0x08: ramen = 1; case 0x00: mbc = 0; break; case 0x03: battery = 1; case 0x01: case 0x02: mbc = 1; break; case 0x06: battery = 1; case 0x05: mbc = 2; break; case 0x0F: case 0x10: case 0x13: battery = 1; case 0x11: case 0x12: mbc = 3; break; case 0x1B: case 0x1E: battery = 1; case 0x19: case 0x1A: case 0x1C: case 0x1D: mbc = 5; break; default: sysfatal("%s: unknown cartridge type %.2x", file, cart[0x147]); } switch(cart[0x148]){ case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: rombanks = 2 << (uint)cart[0x148]; break; case 52: rombanks = 72; break; case 53: rombanks = 80; break; case 54: rombanks = 96; break; default: sysfatal("header field 0x148 (%.2x) invalid", cart[0x148]); } switch(cart[0x149]){ case 0: if(mbc != 2){ rambanks = 0; break; } /*fallthrough*/ case 1: case 2: rambanks = 1; break; case 3: rambanks = 4; break; default: sysfatal("header field 0x149 (%.2x) invalid", cart[0x149]); } if(rambanks > 0){ ram = mallocz(rambanks * 8192, 1); if(ram == nil) sysfatal("malloc: %r"); } if(len < rombanks * 0x4000) sysfatal("cartridge image is too small, %.4x < %.4x", (int)len, rombanks * 0x4000); initdraw(nil, nil, title); originwindow(screen, Pt(0, 0), screen->r.min); p = divpt(addpt(screen->r.min, screen->r.max), 2); picr = (Rectangle){subpt(p, Pt(scale * 80, scale * 72)), addpt(p, Pt(scale * 80, scale * 72))}; bg = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xCCCCCCFF); tmp = allocimage(display, Rect(0, 0, scale * 160, scale * 144), XRGB32, 0, 0); draw(screen, screen->r, bg, nil, ZP); if(ram && battery){ strncpy(buf, file, sizeof buf - 4); s = buf + strlen(buf) - 3; if(s < buf || strcmp(s, ".gb") != 0) s += 3; strcpy(s, ".gbs"); savefd = create(buf, ORDWR|OEXCL, 0666); if(savefd < 0) savefd = open(buf, ORDWR); if(savefd < 0) message("open: %r"); else readn(savefd, ram, rambanks * 8192); atexit(flushram); } } void keyproc(void *) { int fd; char buf[256], *s; Rune r; fd = open("/dev/kbd", OREAD); if(fd < 0) sysfatal("open: %r"); for(;;){ if(read(fd, buf, 256) <= 0) sysfatal("read /dev/kbd: %r"); if(buf[0] == 'c'){ if(utfrune(buf, Kdel)) threadexitsall(nil); if(utfrune(buf, KF|5)) savereq = 1; if(utfrune(buf, KF|6)) loadreq = 1; } if(buf[0] != 'k' && buf[0] != 'K') continue; s = buf + 1; keys = 0; while(*s != 0){ s += chartorune(&r, s); switch(r){ case Kesc: if(paused) qunlock(&pauselock); else qlock(&pauselock); paused = !paused; break; case Kdel: threadexitsall(nil); case Kdown: keys |= 1<<3; break; case Kup: keys |= 1<<2; break; case Kleft: keys |= 1<<1; break; case Kright: keys |= 1<<0; break; case 'x': keys |= 1<<4; break; case 'z': keys |= 1<<5; break; case Kshift: keys |= 1<<6; break; case 10: keys |= 1<<7; break; } } } } void threadmain(int argc, char** argv) { int t; scale = 1; ARGBEGIN{ case 'a': initaudio(); break; case '2': scale = 2; break; case '3': scale = 3; break; default: sysfatal("unknown flag -%c", ARGC()); }ARGEND; if(argc == 0) sysfatal("argument missing"); pc = 0x100; sp = 0xFFFE; R[rA] = 0x01; R[rC] = 0x13; R[rE] = 0xD8; R[rL] = 0x4D; R[rH] = 0x01; Fl = 0xB0; loadrom(argv[0]); mc = initmouse(nil, screen); if(mc == nil) sysfatal("init mouse: %r"); proccreate(keyproc, nil, 8192); for(;;){ if(savereq){ savestate("gb.save"); savereq = 0; } if(loadreq){ loadstate("gb.save"); loadreq = 0; } if(paused){ qlock(&pauselock); qunlock(&pauselock); } t = step(); clock += t; ppuclock += t; divclock += t; audioclock += t; timerclock += t; if(ppuclock >= 456){ ppustep(); ppuclock -= 456; } if(divclock >= 256){ mem[DIV]++; divclock = 0; } if(audioclock >= CPUFREQ / SAMPLE){ audiosample(); audioclock -= CPUFREQ / SAMPLE; } if(timer && timerclock >= timerfreq){ mem[TIMA]++; if(mem[TIMA] == 0){ mem[TIMA] = mem[TMA]; interrupt(INTTIMER); } timerclock = 0; } if(msgclock > 0){ msgclock -= t; if(msgclock <= 0){ draw(screen, screen->r, bg, nil, ZP); msgclock = 0; } } } } void flush(void) { extern uchar pic[160*144*4*3*3]; Mouse m; Point p; static vlong old; vlong new, diff; while(nbrecv(mc->c, &m) > 0) ; if(nbrecvul(mc->resizec) > 0){ if(getwindow(display, Refnone) < 0) sysfatal("resize failed: %r"); p = divpt(addpt(screen->r.min, screen->r.max), 2); picr = (Rectangle){subpt(p, Pt(scale * 80, scale * 72)), addpt(p, Pt(scale * 80, scale * 72))}; if(bg->chan != screen->chan){ freeimage(bg); bg = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xCCCCCCFF); } draw(screen, screen->r, bg, nil, ZP); } if(screen->chan != tmp->chan){ loadimage(tmp, tmp->r, pic, 160*144*4*scale*scale); draw(screen, picr, tmp, nil, ZP); }else loadimage(screen, picr, pic, 160*144*4*scale*scale); flushimage(display, 1); memset(pic, sizeof pic, 0); if(audioout() < 0){ new = nsec(); if(old != 0){ diff = BILLION/60 - (new - old); if(diff >= MILLION) sleep(diff/MILLION); } old = nsec(); } }