]> git.lizzy.rs Git - plan9front.git/blob - sys/src/games/snes/snes.c
games/snes: cpu timing fix
[plan9front.git] / sys / src / games / snes / snes.c
1 #include <u.h>
2 #include <libc.h>
3 #include <thread.h>
4 #include <draw.h>
5 #include <keyboard.h>
6 #include <mouse.h>
7 #include <ctype.h>
8 #include "dat.h"
9 #include "fns.h"
10
11 uchar *prg, *sram;
12 int nprg, nsram, hirom, battery;
13
14 int ppuclock, spcclock, dspclock, stimerclock, saveclock, msgclock, paused, perfclock, cpupause;
15 Mousectl *mc;
16 QLock pauselock;
17 u32int keys;
18 int savefd, scale, profile, mouse, loadreq, savereq;
19 Rectangle picr;
20 Image *tmp, *bg;
21
22 void
23 flushram(void)
24 {
25         if(savefd >= 0)
26                 pwrite(savefd, sram, nsram, 0);
27         saveclock = 0;
28 }
29
30 void
31 loadrom(char *file)
32 {
33         int fd;
34         vlong size;
35
36         fd = open(file, OREAD);
37         if(fd < 0)
38                 sysfatal("open: %r");
39         size = seek(fd, 0, 2);
40         if(size < 0)
41                 sysfatal("seek: %r");
42         if(size == 0)
43                 sysfatal("empty file");
44         if((size & 1023) == 512){
45                 size -= 512;
46                 seek(fd, 512, 0);
47         }else if((size & 1023) == 0)
48                 seek(fd, 0, 0);
49         else
50                 sysfatal("invalid rom size");
51         if(size >= 16*1048576)
52                 sysfatal("rom too big");
53         nprg = (size + 32767) / 32768;
54         prg = malloc(nprg * 32768);
55         if(prg == nil)
56                 sysfatal("malloc: %r");
57         if(readn(fd, prg, size) < size)
58                 sysfatal("read: %r");
59         close(fd);
60         if(hirom < 0){
61                 hirom = 0;
62                 if((memread(0xffd5) & ~0x10) != 0x20)
63                         if((memread(0x1ffd5) & ~0x10) == 0x21)
64                                 hirom = 1;
65                         else
66                                 sysfatal("invalid rom (ffd5 = %.2x, 1ffd5 = %.2x)", memread(0xffd5), memread(0x1ffd5));
67         }
68         if(hirom)
69                 nprg >>= 1;
70         switch(memread(0xffd6)){
71         case 0:
72                 break;
73         case 2:
74                 battery++;
75         case 1:
76                 nsram = memread(0xffd8);
77                 if(nsram == 0)
78                         break;
79                 if(nsram >= 0x0c)
80                         sysfatal("invalid rom (too much ram specified)");
81                 nsram = 1<<(nsram + 10);
82                 sram = malloc(nsram);
83                 if(sram == nil)
84                         sysfatal("malloc: %r");
85                 break;
86         default:
87                 print("unknown rom type %d\n", memread(0xffd5));
88         }
89 }
90
91 void
92 loadbat(char *file)
93 {
94         static char buf[512];
95         char *s;
96
97         if(battery && nsram != 0){
98                 strncpy(buf, file, sizeof buf - 5);
99                 s = buf + strlen(buf) - 4;
100                 if(s < buf || strcmp(s, ".smc") != 0)
101                         s += 4;
102                 strcpy(s, ".sav");
103                 savefd = create(buf, ORDWR | OEXCL, 0666);
104                 if(savefd < 0)
105                         savefd = open(buf, ORDWR);
106                 if(savefd < 0)
107                         message("open: %r");
108                 else
109                         readn(savefd, sram, nsram);
110                 atexit(flushram);
111         }
112 }
113
114 void
115 keyproc(void *)
116 {
117         int fd, k;
118         static char buf[256];
119         char *s;
120         Rune r;
121
122         fd = open("/dev/kbd", OREAD);
123         if(fd < 0)
124                 sysfatal("open: %r");
125         for(;;){
126                 if(read(fd, buf, sizeof(buf) - 1) <= 0)
127                         sysfatal("read /dev/kbd: %r");
128                 if(buf[0] == 'c'){
129                         if(utfrune(buf, KF|5))
130                                 savereq = 1;
131                         if(utfrune(buf, KF|6))
132                                 loadreq = 1;
133                         if(utfrune(buf, Kdel)){
134                                 close(fd);
135                                 threadexitsall(nil);
136                         }
137                         if(utfrune(buf, 't'))
138                                 trace = !trace;
139                 }
140                 if(buf[0] != 'k' && buf[0] != 'K')
141                         continue;
142                 s = buf + 1;
143                 k = 0xffff;
144                 while(*s != 0){
145                         s += chartorune(&r, s);
146                         switch(r){
147                         case Kdel: close(fd); threadexitsall(nil);
148                         case 'z': k |= 1<<31; break;
149                         case 'x': k |= 1<<23; break;
150                         case 'a': k |= 1<<30; break;
151                         case 's': k |= 1<<22; break;
152                         case 'q': k |= 1<<21; break;
153                         case 'w': k |= 1<<20; break;
154                         case Kshift: k |= 1<<29; break;
155                         case 10: k |= 1<<28; break;
156                         case Kup: k |= 1<<27; break;
157                         case Kdown: k |= 1<<26; break;
158                         case Kleft: k |= 1<<25; break;
159                         case Kright: k |= 1<<24; break;
160                         case Kesc:
161                                 if(paused)
162                                         qunlock(&pauselock);
163                                 else
164                                         qlock(&pauselock);
165                                 paused = !paused;
166                                 break;
167                         }
168                 }
169                 if(!mouse)
170                         keys = k;
171         }
172 }
173
174 void
175 screeninit(void)
176 {
177         Point p;
178
179         originwindow(screen, Pt(0, 0), screen->r.min);
180         p = divpt(addpt(screen->r.min, screen->r.max), 2);
181         picr = (Rectangle){subpt(p, Pt(scale * 128, scale * 112)), addpt(p, Pt(scale * 128, scale * 112))};
182         tmp = allocimage(display, Rect(0, 0, scale * 256, scale * 239), RGB15, 0, 0);
183         bg = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xCCCCCCFF);
184         draw(screen, screen->r, bg, nil, ZP);   
185 }
186
187 void
188 timing(void)
189 {
190         static vlong old;
191         static char buf[32];
192         vlong new;
193         
194         new = nsec();
195         if(new != old)
196                 sprint(buf, "%6.2f%%", 1e11 / (new - old));
197         else
198                 buf[0] = 0;
199         draw(screen, Rect(10, 10, 200, 30), bg, nil, ZP);
200         string(screen, Pt(10, 10), display->black, ZP, display->defaultfont, buf);
201         old = nsec();
202 }
203
204 void
205 threadmain(int argc, char **argv)
206 {
207         int t;
208         extern u16int pc;
209
210         scale = 1;
211         hirom = -1;
212         ARGBEGIN {
213         case '2':
214                 scale = 2;
215                 break;
216         case '3':
217                 scale = 3;
218                 break;
219         case 'a':
220                 audioinit();
221                 break;
222         case 's':
223                 battery++;
224                 break;
225         case 'm':
226                 mouse++;
227                 keys = 1<<16;
228                 break;
229         case 'h':
230                 hirom++;
231                 break;
232         case 'T':
233                 profile++;
234                 break;
235         default:
236                 goto usage;
237         } ARGEND;
238         
239         if(argc != 1){
240 usage:
241                 fprint(2, "usage: %s [-23s] rom\n", argv0);
242                 threadexitsall("usage");
243         }
244         loadrom(argv[0]);
245         if(initdraw(nil, nil, nil) < 0)
246                 sysfatal("initdraw: %r");
247         mc = initmouse(nil, screen);
248         if(mc == nil)
249                 sysfatal("initmouse: %r");
250         screeninit();
251         loadbat(argv[0]);
252         proccreate(keyproc, 0, 8192);
253         cpureset();
254         memreset();
255         spcreset();
256         dspreset();
257         for(;;){
258                 if(savereq){
259                         savestate("snes.save");
260                         savereq = 0;
261                 }
262                 if(loadreq){
263                         loadstate("snes.save");
264                         loadreq = 0;
265                 }
266                 if(paused){
267                         qlock(&pauselock);
268                         qunlock(&pauselock);
269                 }
270                 if(cpupause){
271                         t = 40;
272                         cpupause = 0;
273                 }else
274                         t = cpustep();
275                 spcclock -= t;
276                 stimerclock += t;
277                 ppuclock += t;
278                 dspclock += t;
279                 perfclock -= t;
280
281                 while(ppuclock >= 4){
282                         ppustep();
283                         ppuclock -= 4;
284                 }
285                 while(spcclock < 0)
286                         spcclock += spcstep() * SPCDIV;
287                 if(stimerclock >= SPCDIV*16){
288                         spctimerstep();
289                         stimerclock -= SPCDIV*16;
290                 }
291                 if(dspclock >= SPCDIV){
292                         dspstep();
293                         dspclock -= SPCDIV;
294                 }
295                 if(saveclock > 0){
296                         saveclock -= t;
297                         if(saveclock <= 0)
298                                 flushram();
299                 }
300                 if(msgclock > 0){
301                         msgclock -= t;
302                         if(msgclock <= 0){
303                                 draw(screen, screen->r, bg, nil, ZP);
304                                 msgclock = 0;
305                         }
306                 }
307                 if(profile && perfclock <= 0){
308                         perfclock = FREQ;
309                         timing();
310                 }
311         }
312 }
313
314 void
315 flush(void)
316 {
317         extern uchar pic[256*240*2*9];
318         Mouse m;
319         Point p;
320
321         if(nbrecvul(mc->resizec) > 0){
322                 if(getwindow(display, Refnone) < 0)
323                         sysfatal("resize failed: %r");
324                 screeninit();
325         }
326         while(nbrecv(mc->c, &m) > 0)
327                 if(mouse && ptinrect(m.xy, picr)){
328                         p = subpt(m.xy, picr.min);
329                         p.x /= scale;
330                         p.y /= scale;
331                         keys = keys & 0xff3f0000 | p.x | p.y << 8;
332                         if((m.buttons & 1) != 0)
333                                 keys |= 1<<22;
334                         if((m.buttons & 4) != 0)
335                                 keys |= 1<<23;
336                         if((m.buttons & 2) != 0)
337                                 lastkeys = keys;
338                 }
339         loadimage(tmp, tmp->r, pic, 256*239*2*scale*scale);
340         draw(screen, picr, tmp, nil, ZP);
341         flushimage(display, 1);
342         audioout();
343 }
344
345 void
346 message(char *fmt, ...)
347 {
348         va_list va;
349         static char buf[512];
350         
351         va_start(va, fmt);
352         vsnprint(buf, sizeof buf, fmt, va);
353         string(screen, Pt(10, 10), display->black, ZP, display->defaultfont, buf);
354         msgclock = FREQ;
355         va_end(va);
356 }