]> git.lizzy.rs Git - plan9front.git/blob - sys/src/games/gb/gb.c
games/nes: improved time synchronization
[plan9front.git] / sys / src / games / gb / gb.c
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <thread.h>
5 #include <mouse.h>
6 #include <cursor.h>
7 #include <keyboard.h>
8 #include "dat.h"
9 #include "fns.h"
10
11 uchar *cart, *ram;
12 int mbc, rombanks, rambanks, clock, ppuclock, divclock, timerclock, audioclock, msgclock, timerfreq, timer, keys, savefd, savereq, loadreq, scale, paused;
13 Rectangle picr;
14 Image *bg, *tmp;
15 Mousectl *mc;
16 QLock pauselock;
17
18 void
19 message(char *fmt, ...)
20 {
21         va_list va;
22         char buf[512];
23         
24         va_start(va, fmt);
25         vsnprint(buf, sizeof buf, fmt, va);
26         string(screen, Pt(10, 10), display->black, ZP, display->defaultfont, buf);
27         msgclock = CPUFREQ;
28         va_end(va);
29 }
30
31 void
32 loadrom(char *file)
33 {
34         int fd, i;
35         vlong len;
36         u8int ck;
37         char buf[512];
38         char title[17];
39         Point p;
40         char *s;
41         extern int battery, ramen;
42         
43         fd = open(file, OREAD);
44         if(fd < 0)
45                 sysfatal("open: %r");
46         len = seek(fd, 0, 2);
47         if(len < 0)
48                 sysfatal("seek: %r");
49         if(len == 0 || len > 16*1048576)
50                 sysfatal("are you sure this is a ROM?");
51         cart = malloc(len);
52         if(cart == nil)
53                 sysfatal("malloc: %r");
54         seek(fd, 0, 0);
55         if(readn(fd, cart, len) < len)
56                 sysfatal("read: %r");
57         close(fd);
58
59         ck = 0;
60         for(i = 0x134; i <= 0x14C; i++)
61                 ck -= cart[i] + 1;
62         if(ck != cart[0x14D])
63                 sysfatal("checksum mismatch: %.2x != %.2x", ck, cart[0x14D]);
64         memcpy(mem, cart, 32768);
65         memset(title, 0, sizeof(title));
66         memcpy(title, cart+0x134, 16);
67         battery = 0;
68         switch(cart[0x147]){
69         case 0x09:
70                 battery = 1;
71         case 0x08:
72                 ramen = 1;
73         case 0x00:
74                 mbc = 0;
75                 break;
76         case 0x03:
77                 battery = 1;
78         case 0x01: case 0x02:
79                 mbc = 1;
80                 break;
81         case 0x06:
82                 battery = 1;
83         case 0x05:
84                 mbc = 2;
85                 break;
86         case 0x0F: case 0x10: case 0x13:
87                 battery = 1;
88         case 0x11: case 0x12:
89                 mbc = 3;
90                 break;
91         case 0x1B: case 0x1E:
92                 battery = 1;
93         case 0x19: case 0x1A: case 0x1C: case 0x1D:
94                 mbc = 5;
95                 break;
96         default:
97                 sysfatal("%s: unknown cartridge type %.2x", file, cart[0x147]);
98         }
99
100         switch(cart[0x148]){
101         case 0: case 1: case 2:
102         case 3: case 4: case 5:
103         case 6: case 7:
104                 rombanks = 2 << (uint)cart[0x148];
105                 break;
106         case 52:
107                 rombanks = 72;
108                 break;
109         case 53:
110                 rombanks = 80;
111                 break;
112         case 54:
113                 rombanks = 96;
114                 break;
115         default:
116                 sysfatal("header field 0x148 (%.2x) invalid", cart[0x148]);
117         }
118         switch(cart[0x149]){
119         case 0:
120                 if(mbc != 2){
121                         rambanks = 0;
122                         break;
123                 }
124                 /*fallthrough*/
125         case 1: case 2:
126                 rambanks = 1;
127                 break;
128         case 3:
129                 rambanks = 4;
130                 break;
131         default:
132                 sysfatal("header field 0x149 (%.2x) invalid", cart[0x149]);
133         }
134         if(rambanks > 0){
135                 ram = mallocz(rambanks * 8192, 1);
136                 if(ram == nil)
137                         sysfatal("malloc: %r");
138         }
139         if(len < rombanks * 0x4000)
140                 sysfatal("cartridge image is too small, %.4x < %.4x", (int)len, rombanks * 0x4000);
141         initdraw(nil, nil, title);
142         originwindow(screen, Pt(0, 0), screen->r.min);
143         p = divpt(addpt(screen->r.min, screen->r.max), 2);
144         picr = (Rectangle){subpt(p, Pt(scale * 80, scale * 72)), addpt(p, Pt(scale * 80, scale * 72))};
145         bg = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xCCCCCCFF);
146         tmp = allocimage(display, Rect(0, 0, scale * 160, scale * 144), XRGB32, 0, 0);
147         draw(screen, screen->r, bg, nil, ZP);
148         
149         if(ram && battery){
150                 strncpy(buf, file, sizeof buf - 4);
151                 s = buf + strlen(buf) - 3;
152                 if(s < buf || strcmp(s, ".gb") != 0)
153                         s += 3;
154                 strcpy(s, ".gbs");
155                 savefd = create(buf, ORDWR|OEXCL, 0666);
156                 if(savefd < 0)
157                         savefd = open(buf, ORDWR);
158                 if(savefd < 0)
159                         message("open: %r");
160                 else
161                         readn(savefd, ram, rambanks * 8192);
162                 atexit(flushram);
163         }
164 }
165
166 void
167 keyproc(void *)
168 {
169         int fd;
170         char buf[256], *s;
171         Rune r;
172         
173         fd = open("/dev/kbd", OREAD);
174         if(fd < 0)
175                 sysfatal("open: %r");
176         for(;;){
177                 if(read(fd, buf, 256) <= 0)
178                         sysfatal("read /dev/kbd: %r");
179                 if(buf[0] == 'c'){
180                         if(utfrune(buf, Kdel))
181                                 threadexitsall(nil);
182                         if(utfrune(buf, KF|5))
183                                 savereq = 1;
184                         if(utfrune(buf, KF|6))
185                                 loadreq = 1;
186                 }
187                 if(buf[0] != 'k' && buf[0] != 'K')
188                         continue;
189                 s = buf + 1;
190                 keys = 0;
191                 while(*s != 0){
192                         s += chartorune(&r, s);
193                         switch(r){
194                         case Kesc:
195                                 if(paused)
196                                         qunlock(&pauselock);
197                                 else
198                                         qlock(&pauselock);
199                                 paused = !paused;
200                                 break;
201                         case Kdel:
202                                 threadexitsall(nil);
203                         case Kdown:
204                                 keys |= 1<<3;
205                                 break;
206                         case Kup:
207                                 keys |= 1<<2;
208                                 break;
209                         case Kleft:
210                                 keys |= 1<<1;
211                                 break;
212                         case Kright:
213                                 keys |= 1<<0;
214                                 break;
215                         case 'x':
216                                 keys |= 1<<4;
217                                 break;
218                         case 'z':
219                                 keys |= 1<<5;
220                                 break;
221                         case Kshift:
222                                 keys |= 1<<6;
223                                 break;
224                         case 10:
225                                 keys |= 1<<7;
226                                 break;
227                         }
228                 }
229         }
230 }
231
232 void
233 threadmain(int argc, char** argv)
234 {
235         int t;
236
237         scale = 1;
238         ARGBEGIN{
239         case 'a':
240                 initaudio();
241                 break;
242         case '2':
243                 scale = 2;
244                 break;
245         case '3':
246                 scale = 3;
247                 break;
248         default:
249                 sysfatal("unknown flag -%c", ARGC());
250         }ARGEND;
251         if(argc == 0)
252                 sysfatal("argument missing");
253         pc = 0x100;
254         sp = 0xFFFE;
255         R[rA] = 0x01;
256         R[rC] = 0x13;
257         R[rE] = 0xD8;
258         R[rL] = 0x4D;
259         R[rH] = 0x01;
260         Fl = 0xB0;
261         loadrom(argv[0]);
262         mc = initmouse(nil, screen);
263         if(mc == nil)
264                 sysfatal("init mouse: %r");
265         proccreate(keyproc, nil, 8192);
266         for(;;){
267                 if(savereq){
268                         savestate("gb.save");
269                         savereq = 0;
270                 }
271                 if(loadreq){
272                         loadstate("gb.save");
273                         loadreq = 0;
274                 }
275                 if(paused){
276                         qlock(&pauselock);
277                         qunlock(&pauselock);
278                 }
279                 t = step();
280                 clock += t;
281                 ppuclock += t;
282                 divclock += t;
283                 audioclock += t;
284                 timerclock += t;
285                 if(ppuclock >= 456){
286                         ppustep();
287                         ppuclock -= 456;
288                 }
289                 if(divclock >= 256){
290                         mem[DIV]++;
291                         divclock = 0;
292                 }
293                 if(audioclock >= CPUFREQ / SAMPLE){
294                         audiosample();
295                         audioclock -= CPUFREQ / SAMPLE;
296                 }
297                 if(timer && timerclock >= timerfreq){
298                         mem[TIMA]++;
299                         if(mem[TIMA] == 0){
300                                 mem[TIMA] = mem[TMA];
301                                 interrupt(INTTIMER);
302                         }
303                         timerclock = 0;
304                 }
305                 if(msgclock > 0){
306                         msgclock -= t;
307                         if(msgclock <= 0){
308                                 draw(screen, screen->r, bg, nil, ZP);
309                                 msgclock = 0;
310                         }
311                 }
312         }
313 }
314
315 void
316 flush(void)
317 {
318         extern uchar pic[160*144*4*3*3];
319         Mouse m;
320         Point p;
321         static vlong old;
322         vlong new, diff;
323
324         while(nbrecv(mc->c, &m) > 0)
325                 ;
326         if(nbrecvul(mc->resizec) > 0){
327                 if(getwindow(display, Refnone) < 0)
328                         sysfatal("resize failed: %r");
329                 p = divpt(addpt(screen->r.min, screen->r.max), 2);
330                 picr = (Rectangle){subpt(p, Pt(scale * 80, scale * 72)), addpt(p, Pt(scale * 80, scale * 72))};
331                 if(bg->chan != screen->chan){
332                         freeimage(bg);
333                         bg = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xCCCCCCFF);
334                 }
335                 draw(screen, screen->r, bg, nil, ZP);
336         }
337         if(screen->chan != tmp->chan){
338                 loadimage(tmp, tmp->r, pic, 160*144*4*scale*scale);
339                 draw(screen, picr, tmp, nil, ZP);
340         }else
341                 loadimage(screen, picr, pic, 160*144*4*scale*scale);
342         flushimage(display, 1);
343         memset(pic, sizeof pic, 0);
344         if(audioout() < 0){
345                 new = nsec();
346                 if(old != 0){
347                         diff = BILLION/60 - (new - old);
348                         if(diff >= MILLION)
349                                 sleep(diff/MILLION);
350                 }
351                 old = nsec();
352         }
353 }