some work on PSP CLUT
[libpicofe.git] / psp / emu.c
1 #include <sys/stat.h>
2 #include <sys/types.h>
3 #include <sys/syslimits.h> // PATH_MAX
4
5 #include <pspthreadman.h>
6 #include <pspdisplay.h>
7 #include <psputils.h>
8 #include <pspgu.h>
9
10 #include "psp.h"
11 #include "menu.h"
12 #include "emu.h"
13 #include "../common/emu.h"
14 #include "../common/lprintf.h"
15 #include "../../Pico/PicoInt.h"
16
17 #ifdef BENCHMARK
18 #define OSD_FPS_X 220
19 #else
20 #define OSD_FPS_X 260
21 #endif
22
23 char romFileName[PATH_MAX];
24 unsigned char *PicoDraw2FB = (unsigned char *)VRAM_CACHED_STUFF + 8; // +8 to be able to skip border with 1 quadword..
25 int engineState;
26
27 static int combo_keys = 0, combo_acts = 0; // keys and actions which need button combos
28 static unsigned int noticeMsgTime = 0;
29 int reset_timing = 0; // do we need this?
30
31
32 static void blit(const char *fps, const char *notice);
33 static void clearArea(int full);
34
35 void emu_noticeMsgUpdated(void)
36 {
37         noticeMsgTime = sceKernelGetSystemTimeLow();
38 }
39
40 void emu_getMainDir(char *dst, int len)
41 {
42         if (len > 0) *dst = 0;
43 }
44
45 static void emu_msg_cb(const char *msg)
46 {
47         void *fb = psp_video_get_active_fb();
48
49         memset32((int *)((char *)fb + 512*264*2), 0, 512*8*2/4);
50         emu_textOut16(4, 264, msg);
51         noticeMsgTime = sceKernelGetSystemTimeLow() - 2000000;
52
53         /* assumption: emu_msg_cb gets called only when something slow is about to happen */
54         reset_timing = 1;
55 }
56
57 void emu_stateCb(const char *str)
58 {
59         clearArea(0);
60         blit("", str);
61 }
62
63 static void emu_msg_tray_open(void)
64 {
65         strcpy(noticeMsg, "CD tray opened");
66         noticeMsgTime = sceKernelGetSystemTimeLow();
67 }
68
69
70 void emu_Init(void)
71 {
72         // make dirs for saves, cfgs, etc.
73         mkdir("mds", 0777);
74         mkdir("srm", 0777);
75         mkdir("brm", 0777);
76         mkdir("cfg", 0777);
77
78         PicoInit();
79         PicoMessage = emu_msg_cb;
80         PicoMCDopenTray = emu_msg_tray_open;
81         PicoMCDcloseTray = menu_loop_tray;
82 }
83
84 void emu_Deinit(void)
85 {
86         // save SRAM
87         if ((currentConfig.EmuOpt & 1) && SRam.changed) {
88                 emu_SaveLoadGame(0, 1);
89                 SRam.changed = 0;
90         }
91
92         if (!(currentConfig.EmuOpt & 0x20)) {
93                 FILE *f = fopen(PicoConfigFile, "r+b");
94                 if (!f) emu_WriteConfig(0);
95                 else {
96                         // if we already have config, reload it, except last ROM
97                         fseek(f, sizeof(currentConfig.lastRomFile), SEEK_SET);
98                         fread(&currentConfig.EmuOpt, 1, sizeof(currentConfig) - sizeof(currentConfig.lastRomFile), f);
99                         fseek(f, 0, SEEK_SET);
100                         fwrite(&currentConfig, 1, sizeof(currentConfig), f);
101                         fflush(f);
102                         fclose(f);
103                 }
104         }
105
106         PicoExit();
107 }
108
109 void emu_setDefaultConfig(void)
110 {
111         memset(&currentConfig, 0, sizeof(currentConfig));
112         currentConfig.lastRomFile[0] = 0;
113         currentConfig.EmuOpt  = 0x1f | 0x680; // | confirm_save, cd_leds, 16bit rend
114         currentConfig.PicoOpt = 0x07 | 0xc00; // | cd_pcm, cd_cdda
115         currentConfig.PsndRate = 22050;
116         currentConfig.PicoRegion = 0; // auto
117         currentConfig.PicoAutoRgnOrder = 0x184; // US, EU, JP
118         currentConfig.Frameskip = -1; // auto
119         currentConfig.volume = 50;
120         currentConfig.CPUclock = 222;
121         currentConfig.KeyBinds[ 4] = 1<<0; // SACB RLDU
122         currentConfig.KeyBinds[ 6] = 1<<1;
123         currentConfig.KeyBinds[ 7] = 1<<2;
124         currentConfig.KeyBinds[ 5] = 1<<3;
125         currentConfig.KeyBinds[14] = 1<<4;
126         currentConfig.KeyBinds[13] = 1<<5;
127         currentConfig.KeyBinds[15] = 1<<6;
128         currentConfig.KeyBinds[ 3] = 1<<7;
129         currentConfig.KeyBinds[23] = 1<<26; // switch rend
130         currentConfig.KeyBinds[ 8] = 1<<27; // save state
131         currentConfig.KeyBinds[ 9] = 1<<28; // load state
132         currentConfig.PicoCDBuffers = 0;
133         currentConfig.scaling = 0;
134 }
135
136
137 static unsigned short __attribute__((aligned(16))) localPal[0x100];
138
139 struct Vertex
140 {
141         short u,v;
142         short x,y,z;
143 };
144
145 static void EmuScanPrepare(void)
146 {
147         HighCol = VRAM_STUFF;
148
149 #if 0
150         sceGuSync(0,0); // sync with prev
151         sceGuStart(GU_DIRECT, guCmdList);
152 //      sceGuDispBuffer(480, 272, psp_screen == VRAM_FB0 ? VRAMOFFS_FB1 : VRAMOFFS_FB0, 512);
153         sceGuDrawBuffer(GU_PSM_5650, psp_screen == VRAM_FB0 ? VRAMOFFS_FB0 : VRAMOFFS_FB1, 512); // point to back fb?
154         sceGuFinish();
155 #endif
156 }
157
158 static int EmuScan16(unsigned int num, void *sdata)
159 {
160 //      struct Vertex* vertices;
161
162         if (!(Pico.video.reg[1]&8)) num += 8;
163         //DrawLineDest = (unsigned short *) psp_screen + 512*(num+1);
164         HighCol = (unsigned char *)psp_screen + num*512;
165
166 #if 0
167         sceGuSync(0,0); // sync with prev
168         sceGuStart(GU_DIRECT, guCmdList);
169
170         if (Pico.m.dirtyPal) {
171                 int i, *dpal = (void *)localPal, *spal = (int *)Pico.cram;
172                 Pico.m.dirtyPal = 0;
173                 for (i = 0x3f/2; i >= 0; i--)
174                         dpal[i] = ((spal[i]&0x000f000f)<< 1)|((spal[i]&0x00f000f0)<<3)|((spal[i]&0x0f000f00)<<4);
175
176                 sceGuClutLoad((256/8), localPal); // upload 32*8 entries (256)
177         }
178
179         // setup CLUT texture
180
181 //      sceGuClutMode(GU_PSM_5650,0,0xff,0);
182 //      sceGuClutLoad((256/8), localPal); // upload 32*8 entries (256)
183 //      sceGuTexMode(GU_PSM_T8,0,0,0); // 8-bit image
184 //      sceGuTexImage(0,512,1/*512*/,512,VRAM_STUFF);
185 //      sceGuTexFunc(GU_TFX_REPLACE,GU_TCC_RGB);
186 //      sceGuTexFilter(GU_LINEAR,GU_LINEAR);
187 //      sceGuTexScale(1.0f,1.0f);
188 //      sceGuTexOffset(0.0f,0.0f);
189 //      sceGuAmbientColor(0xffffffff);
190
191         // render sprite
192
193 //      sceGuColor(0xffffffff);
194         vertices = (struct Vertex*)sceGuGetMemory(2 * sizeof(struct Vertex));
195         vertices[0].u = 0; vertices[0].v = 0;
196         vertices[0].x = 0; vertices[0].y = num; vertices[0].z = 0;
197         vertices[1].u = 320; vertices[1].v = 512;
198         vertices[1].x = 320; vertices[1].y = num+1; vertices[1].z = 0;
199         //sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,vertices);
200
201         sceGuFinish();
202 #endif
203         return 0;
204 }
205
206
207 static void draw2_clut(void)
208 {
209         struct Vertex* vertices;
210         int x;
211
212         sceKernelDcacheWritebackAll(); // for PicoDraw2FB
213
214         sceGuSync(0,0); // sync with prev
215         sceGuStart(GU_DIRECT, guCmdList);
216 //      sceGuDispBuffer(480, 272, psp_screen == VRAM_FB0 ? VRAMOFFS_FB1 : VRAMOFFS_FB0, 512);
217         sceGuDrawBuffer(GU_PSM_5650, psp_screen == VRAM_FB0 ? VRAMOFFS_FB0 : VRAMOFFS_FB1, 512); // point to back fb?
218
219         if (Pico.m.dirtyPal) {
220                 int i, *dpal = (void *)localPal, *spal = (int *)Pico.cram;
221                 Pico.m.dirtyPal = 0;
222                 for (i = 0x3f/2; i >= 0; i--)
223                         dpal[i] = ((spal[i]&0x000f000f)<< 1)|((spal[i]&0x00f000f0)<<3)|((spal[i]&0x0f000f00)<<4);
224
225                 sceGuClutLoad((256/8), localPal); // upload 32*8 entries (256)
226         }
227
228         #define SLICE_WIDTH 32
229
230         for (x = 0; x < 320; x += SLICE_WIDTH)
231         {
232                 // render sprite
233                 vertices = (struct Vertex*)sceGuGetMemory(2 * sizeof(struct Vertex));
234                 vertices[0].u = vertices[0].x = x;
235                 vertices[0].v = vertices[0].y = 0;
236                 vertices[0].z = 0;
237                 vertices[1].u = vertices[1].x = x + SLICE_WIDTH;
238                 vertices[1].v = vertices[1].y = 224;
239                 vertices[1].z = 0;
240                 sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,vertices);
241         }
242
243         sceGuFinish();
244 }
245
246
247
248 static int EmuScan8(unsigned int num, void *sdata)
249 {
250         // draw like the fast renderer
251         // TODO?
252         //if (!(Pico.video.reg[1]&8)) num += 8;
253         //HighCol = gfx_buffer + 328*(num+1);
254
255         return 0;
256 }
257
258 static void osd_text(int x, const char *text)
259 {
260         int len = strlen(text) * 8 / 2;
261         int *p, h;
262         for (h = 0; h < 8; h++) {
263                 p = (int *) ((unsigned short *) psp_screen+x+512*(264+h));
264                 p = (int *) ((int)p & ~3); // align
265                 memset32(p, 0, len);
266         }
267         emu_textOut16(x, 264, text);
268 }
269
270
271 static void cd_leds(void)
272 {
273         static int old_reg = 0;
274         unsigned int col_g, col_r, *p;
275
276         if (!((Pico_mcd->s68k_regs[0] ^ old_reg) & 3)) return; // no change
277         old_reg = Pico_mcd->s68k_regs[0];
278
279         p = (unsigned int *)((short *)psp_screen + 512*2+4+2);
280         col_g = (old_reg & 2) ? 0x06000600 : 0;
281         col_r = (old_reg & 1) ? 0xc000c000 : 0;
282         *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += 512/2 - 12/2;
283         *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += 512/2 - 12/2;
284         *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r;
285 }
286
287
288 static void blit(const char *fps, const char *notice)
289 {
290         int emu_opt = currentConfig.EmuOpt;
291
292         if (PicoOpt&0x10)
293         {
294 #if 1
295                 draw2_clut();
296 #else
297                 extern void amips_clut(unsigned short *dst, unsigned char *src, unsigned short *pal, int count);
298                 int i; // , lines_flags = 224;
299                 unsigned short *pd = psp_screen;
300                 unsigned char  *ps = PicoDraw2FB+328*8+8;
301                 // 8bit fast renderer
302                 if (Pico.m.dirtyPal) {
303                         int *dpal = (void *)localPal;
304                         int *spal = (int *)Pico.cram;
305                         Pico.m.dirtyPal = 0;
306                         for (i = 0x3f/2; i >= 0; i--)
307                                 dpal[i] = ((spal[i]&0x000f000f)<< 1)|((spal[i]&0x00f000f0)<<3)|((spal[i]&0x0f000f00)<<4);
308                 }
309                 // if (!(Pico.video.reg[12]&1)) lines_flags|=0x10000;
310                 // if (currentConfig.EmuOpt&0x4000)
311                 //      lines_flags|=0x40000; // (Pico.m.frame_count&1)?0x20000:0x40000;
312                 //vidCpy8to16((unsigned short *)giz_screen+321*8, PicoDraw2FB+328*8, localPal, lines_flags);
313                 for (i = 224; i > 0; i--, pd+=512, ps+=328)
314                         amips_clut(pd, ps, localPal, 320);
315 #endif
316         }
317 #if 0
318         else if (!(emu_opt&0x80))
319         {
320                 int lines_flags;
321                 // 8bit accurate renderer
322                 if (Pico.m.dirtyPal) {
323                         Pico.m.dirtyPal = 0;
324                         vidConvCpyRGB565(localPal, Pico.cram, 0x40);
325                         if (Pico.video.reg[0xC]&8) { // shadow/hilight mode
326                                 //vidConvCpyRGB32sh(localPal+0x40, Pico.cram, 0x40);
327                                 //vidConvCpyRGB32hi(localPal+0x80, Pico.cram, 0x40); // TODO?
328                                 blockcpy(localPal+0xc0, localPal+0x40, 0x40*2);
329                                 localPal[0xc0] = 0x0600;
330                                 localPal[0xd0] = 0xc000;
331                                 localPal[0xe0] = 0x0000; // reserved pixels for OSD
332                                 localPal[0xf0] = 0xffff;
333                         }
334                         /* no support
335                         else if (rendstatus & 0x20) { // mid-frame palette changes
336                                 vidConvCpyRGB565(localPal+0x40, HighPal, 0x40);
337                                 vidConvCpyRGB565(localPal+0x80, HighPal+0x40, 0x40);
338                         } */
339                 }
340                 lines_flags = (Pico.video.reg[1]&8) ? 240 : 224;
341                 if (!(Pico.video.reg[12]&1)) lines_flags|=0x10000;
342                 if (currentConfig.EmuOpt&0x4000)
343                         lines_flags|=0x40000; // (Pico.m.frame_count&1)?0x20000:0x40000;
344                 vidCpy8to16((unsigned short *)giz_screen+321*8, PicoDraw2FB+328*8, localPal, lines_flags);
345         }
346 #endif
347
348         if (notice || (emu_opt & 2)) {
349                 if (notice)      osd_text(4, notice);
350                 if (emu_opt & 2) osd_text(OSD_FPS_X, fps);
351         }
352
353         if ((emu_opt & 0x400) && (PicoMCD & 1))
354                 cd_leds();
355
356         sceGuSync(0,0);
357         psp_video_flip(0);
358 }
359
360 // clears whole screen or just the notice area (in all buffers)
361 static void clearArea(int full)
362 {
363         if (full) {
364                 memset32(psp_screen, 0, 512*272*2/4);
365                 psp_video_flip(0);
366                 memset32(psp_screen, 0, 512*272*2/4);
367         } else {
368                 void *fb = psp_video_get_active_fb();
369                 memset32((int *)((char *)psp_screen + 512*264*2), 0, 512*8*2/4);
370                 memset32((int *)((char *)fb         + 512*264*2), 0, 512*8*2/4);
371         }
372 }
373
374 static void vidResetMode(void)
375 {
376         // setup GU
377         sceGuSync(0,0); // sync with prev
378         sceGuStart(GU_DIRECT, guCmdList);
379
380         sceGuClutMode(GU_PSM_5650,0,0xff,0);
381         //sceGuClutLoad((256/8), localPal); // upload 32*8 entries (256)
382         sceGuTexMode(GU_PSM_T8,0,0,0); // 8-bit image
383         sceGuTexFunc(GU_TFX_REPLACE,GU_TCC_RGB);
384         sceGuTexFilter(GU_LINEAR,GU_LINEAR);
385         sceGuTexScale(1.0f,1.0f);
386         sceGuTexOffset(0.0f,0.0f);
387         sceGuAmbientColor(0xffffffff);
388         sceGuColor(0xffffffff);
389
390
391         if (PicoOpt&0x10) {
392                 sceGuTexImage(0,512,512,512,(char *)VRAM_STUFF + 8*512+16);
393
394         } else if (currentConfig.EmuOpt&0x80) {
395                 PicoDrawSetColorFormat(/*1*/-1);
396                 PicoScan = EmuScan16;
397
398                 sceGuTexImage(0,512,1/*512*/,512,VRAM_STUFF);
399
400         } else {
401                 PicoDrawSetColorFormat(-1);
402                 PicoScan = EmuScan8;
403         }
404         if ((PicoOpt&0x10) || !(currentConfig.EmuOpt&0x80)) {
405                 // setup pal for 8-bit modes
406                 localPal[0xc0] = 0x0600;
407                 localPal[0xd0] = 0xc000;
408                 localPal[0xe0] = 0x0000; // reserved pixels for OSD
409                 localPal[0xf0] = 0xffff;
410         }
411         Pico.m.dirtyPal = 1;
412
413         sceGuFinish();
414         sceGuSync(0,0);
415
416         clearArea(1);
417 }
418 /*
419 static void updateSound(int len)
420 {
421         if (PicoOpt&8) len<<=1;
422
423         // TODO..
424 }
425 */
426
427 static void SkipFrame(void)
428 {
429         PicoSkipFrame=1;
430         PicoFrame();
431         PicoSkipFrame=0;
432 }
433
434 void emu_forcedFrame(void)
435 {
436         int po_old = PicoOpt;
437         int eo_old = currentConfig.EmuOpt;
438
439         PicoOpt &= ~0x0010;
440         PicoOpt |=  0x4080; // soft_scale | acc_sprites
441         currentConfig.EmuOpt |= 0x80;
442
443         PicoDrawSetColorFormat(1);
444         PicoScan = EmuScan16;
445         EmuScanPrepare();
446         Pico.m.dirtyPal = 1;
447         PicoFrameDrawOnly();
448
449         PicoOpt = po_old;
450         currentConfig.EmuOpt = eo_old;
451 }
452
453
454 static void RunEvents(unsigned int which)
455 {
456         if (which & 0x1800) // save or load (but not both)
457         {
458                 int do_it = 1;
459
460                 if ( emu_checkSaveFile(state_slot) &&
461                                 (( (which & 0x1000) && (currentConfig.EmuOpt & 0x800)) || // load
462                                  (!(which & 0x1000) && (currentConfig.EmuOpt & 0x200))) ) // save
463                 {
464                         int keys;
465                         blit("", (which & 0x1000) ? "LOAD STATE? (X=yes, O=no)" : "OVERWRITE SAVE? (X=yes, O=no)");
466                         while( !((keys = psp_pad_read(1)) & (BTN_X|BTN_CIRCLE)) )
467                                 psp_msleep(50);
468                         if (keys & BTN_CIRCLE) do_it = 0;
469                         while(  ((keys = psp_pad_read(1)) & (BTN_X|BTN_CIRCLE)) ) // wait for release
470                                 psp_msleep(50);
471                         clearArea(0);
472                 }
473
474                 if (do_it)
475                 {
476                         osd_text(4, (which & 0x1000) ? "LOADING GAME" : "SAVING GAME");
477                         PicoStateProgressCB = emu_stateCb;
478                         emu_SaveLoadGame((which & 0x1000) >> 12, 0);
479                         PicoStateProgressCB = NULL;
480                         psp_msleep(0);
481                 }
482
483                 reset_timing = 1;
484         }
485         if (which & 0x0400) // switch renderer
486         {
487                 if (PicoOpt&0x10) { PicoOpt&=~0x10; currentConfig.EmuOpt |=  0x80; }
488                 else              { PicoOpt|= 0x10; currentConfig.EmuOpt &= ~0x80; }
489
490                 vidResetMode();
491
492                 if (PicoOpt&0x10) {
493                         strcpy(noticeMsg, " 8bit fast renderer");
494                 } else if (currentConfig.EmuOpt&0x80) {
495                         strcpy(noticeMsg, "16bit accurate renderer");
496                 } else {
497                         strcpy(noticeMsg, " 8bit accurate renderer");
498                 }
499
500                 noticeMsgTime = sceKernelGetSystemTimeLow();
501         }
502         if (which & 0x0300)
503         {
504                 if(which&0x0200) {
505                         state_slot -= 1;
506                         if(state_slot < 0) state_slot = 9;
507                 } else {
508                         state_slot += 1;
509                         if(state_slot > 9) state_slot = 0;
510                 }
511                 sprintf(noticeMsg, "SAVE SLOT %i [%s]", state_slot, emu_checkSaveFile(state_slot) ? "USED" : "FREE");
512                 noticeMsgTime = sceKernelGetSystemTimeLow();
513         }
514 }
515
516 static void updateKeys(void)
517 {
518         unsigned int keys, allActions[2] = { 0, 0 }, events;
519         static unsigned int prevEvents = 0;
520         int i;
521
522         keys = psp_pad_read(0);
523         if (keys & PSP_CTRL_HOME)
524                 sceDisplayWaitVblankStart();
525
526         if (keys & BTN_SELECT)
527                 engineState = PGS_Menu;
528
529         keys &= CONFIGURABLE_KEYS;
530
531         for (i = 0; i < 32; i++)
532         {
533                 if (keys & (1 << i))
534                 {
535                         int pl, acts = currentConfig.KeyBinds[i];
536                         if (!acts) continue;
537                         pl = (acts >> 16) & 1;
538                         if (combo_keys & (1 << i))
539                         {
540                                 int u = i+1, acts_c = acts & combo_acts;
541                                 // let's try to find the other one
542                                 if (acts_c)
543                                         for (; u < 32; u++)
544                                                 if ( (currentConfig.KeyBinds[u] & acts_c) && (keys & (1 << u)) ) {
545                                                         allActions[pl] |= acts_c;
546                                                         keys &= ~((1 << i) | (1 << u));
547                                                         break;
548                                                 }
549                                 // add non-combo actions if combo ones were not found
550                                 if (!acts_c || u == 32)
551                                         allActions[pl] |= acts & ~combo_acts;
552                         } else {
553                                 allActions[pl] |= acts;
554                         }
555                 }
556         }
557
558         PicoPad[0] = (unsigned short) allActions[0];
559         PicoPad[1] = (unsigned short) allActions[1];
560
561         events = (allActions[0] | allActions[1]) >> 16;
562
563         // volume is treated in special way and triggered every frame
564         if ((events & 0x6000) && PsndOut != NULL)
565         {
566                 int vol = currentConfig.volume;
567                 if (events & 0x2000) {
568                         if (vol < 100) vol++;
569                 } else {
570                         if (vol >   0) vol--;
571                 }
572                 // FrameworkAudio_SetVolume(vol, vol); // TODO
573                 sprintf(noticeMsg, "VOL: %02i ", vol);
574                 noticeMsgTime = sceKernelGetSystemTimeLow();
575                 currentConfig.volume = vol;
576         }
577
578         events &= ~prevEvents;
579         if (events) RunEvents(events);
580         if (movie_data) emu_updateMovie();
581
582         prevEvents = (allActions[0] | allActions[1]) >> 16;
583 }
584
585 static void find_combos(void)
586 {
587         int act, u;
588
589         // find out which keys and actions are combos
590         combo_keys = combo_acts = 0;
591         for (act = 0; act < 32; act++)
592         {
593                 int keyc = 0;
594                 if (act == 16 || act == 17) continue; // player2 flag
595                 for (u = 0; u < 32; u++)
596                 {
597                         if (currentConfig.KeyBinds[u] & (1 << act)) keyc++;
598                 }
599                 if (keyc > 1)
600                 {
601                         // loop again and mark those keys and actions as combo
602                         for (u = 0; u < 32; u++)
603                         {
604                                 if (currentConfig.KeyBinds[u] & (1 << act)) {
605                                         combo_keys |= 1 << u;
606                                         combo_acts |= 1 << act;
607                                 }
608                         }
609                 }
610         }
611 }
612
613
614 static void simpleWait(unsigned int until)
615 {
616         unsigned int tval;
617         int diff;
618
619         tval = sceKernelGetSystemTimeLow();
620         diff = (int)until - (int)tval;
621         if (diff >= 512 && diff < 100*1024)
622                 sceKernelDelayThread(diff);
623 }
624
625 void emu_Loop(void)
626 {
627         //static int PsndRate_old = 0, PicoOpt_old = 0, pal_old = 0;
628         char fpsbuff[24]; // fps count c string
629         unsigned int tval, tval_prev = 0, tval_thissec = 0; // timing
630         int frames_done = 0, frames_shown = 0, oldmodes = 0;
631         int target_fps, target_frametime, lim_time, tval_diff, i;
632         char *notice = NULL;
633
634         lprintf("entered emu_Loop()\n");
635
636         fpsbuff[0] = 0;
637
638         if (currentConfig.CPUclock != psp_get_cpu_clock()) {
639                 lprintf("setting cpu clock to %iMHz... ", currentConfig.CPUclock);
640                 i = psp_set_cpu_clock(currentConfig.CPUclock);
641                 lprintf(i ? "failed\n" : "done\n");
642                 currentConfig.CPUclock = psp_get_cpu_clock();
643         }
644
645         // make sure we are in correct mode
646         vidResetMode();
647         Pico.m.dirtyPal = 1;
648         oldmodes = ((Pico.video.reg[12]&1)<<2) ^ 0xc;
649         find_combos();
650
651         // pal/ntsc might have changed, reset related stuff
652         target_fps = Pico.m.pal ? 50 : 60;
653         target_frametime = Pico.m.pal ? (1000000<<8)/50 : (1000000<<8)/60+1;
654         reset_timing = 1;
655
656         // prepare CD buffer
657         if (PicoMCD & 1) PicoCDBufferInit();
658
659         // prepare sound stuff
660         PsndOut = NULL;
661 #if 0 // TODO
662         if (currentConfig.EmuOpt & 4)
663         {
664                 int ret, snd_excess_add, stereo;
665                 if (PsndRate != PsndRate_old || (PicoOpt&0x0b) != (PicoOpt_old&0x0b) || Pico.m.pal != pal_old) {
666                         sound_rerate(Pico.m.frame_count ? 1 : 0);
667                 }
668                 stereo=(PicoOpt&8)>>3;
669                 snd_excess_add = ((PsndRate - PsndLen*target_fps)<<16) / target_fps;
670                 snd_cbuf_samples = (PsndRate<<stereo) * 16 / target_fps;
671                 lprintf("starting audio: %i len: %i (ex: %04x) stereo: %i, pal: %i\n",
672                         PsndRate, PsndLen, snd_excess_add, stereo, Pico.m.pal);
673                 ret = FrameworkAudio_Init(PsndRate, snd_cbuf_samples, stereo);
674                 if (ret != 0) {
675                         lprintf("FrameworkAudio_Init() failed: %i\n", ret);
676                         sprintf(noticeMsg, "sound init failed (%i), snd disabled", ret);
677                         noticeMsgTime = sceKernelGetSystemTimeLow();
678                         currentConfig.EmuOpt &= ~4;
679                 } else {
680                         FrameworkAudio_SetVolume(currentConfig.volume, currentConfig.volume);
681                         PicoWriteSound = updateSound;
682                         snd_cbuff = FrameworkAudio_56448Buffer();
683                         PsndOut = snd_cbuff + snd_cbuf_samples / 2; // start writing at the middle
684                         snd_all_samples = 0;
685                         PsndRate_old = PsndRate;
686                         PicoOpt_old  = PicoOpt;
687                         pal_old = Pico.m.pal;
688                 }
689         }
690 #endif
691
692         // loop?
693         while (engineState == PGS_Running)
694         {
695                 int modes;
696
697                 tval = sceKernelGetSystemTimeLow();
698                 if (reset_timing || tval < tval_prev) {
699                         //stdbg("timing reset");
700                         reset_timing = 0;
701                         tval_thissec = tval;
702                         frames_shown = frames_done = 0;
703                 }
704
705                 // show notice message?
706                 if (noticeMsgTime) {
707                         static int noticeMsgSum;
708                         if (tval - noticeMsgTime > 2000000) { // > 2.0 sec
709                                 noticeMsgTime = 0;
710                                 clearArea(0);
711                                 notice = 0;
712                         } else {
713                                 int sum = noticeMsg[0]+noticeMsg[1]+noticeMsg[2];
714                                 if (sum != noticeMsgSum) { clearArea(0); noticeMsgSum = sum; }
715                                 notice = noticeMsg;
716                         }
717                 }
718
719                 // check for mode changes
720                 modes = ((Pico.video.reg[12]&1)<<2)|(Pico.video.reg[1]&8);
721                 if (modes != oldmodes) {
722                         oldmodes = modes;
723                         clearArea(1);
724                 }
725
726                 // second passed?
727                 if (tval - tval_thissec >= 1000000)
728                 {
729 #ifdef BENCHMARK
730                         static int bench = 0, bench_fps = 0, bench_fps_s = 0, bfp = 0, bf[4];
731                         if(++bench == 10) {
732                                 bench = 0;
733                                 bench_fps_s = bench_fps;
734                                 bf[bfp++ & 3] = bench_fps;
735                                 bench_fps = 0;
736                         }
737                         bench_fps += frames_shown;
738                         sprintf(fpsbuff, "%02i/%02i/%02i", frames_shown, bench_fps_s, (bf[0]+bf[1]+bf[2]+bf[3])>>2);
739 #else
740                         if(currentConfig.EmuOpt & 2)
741                                 sprintf(fpsbuff, "%02i/%02i", frames_shown, frames_done);
742 #endif
743                         tval_thissec += 1000000;
744
745                         if (currentConfig.Frameskip < 0) {
746                                 frames_done  -= target_fps; if (frames_done  < 0) frames_done  = 0;
747                                 frames_shown -= target_fps; if (frames_shown < 0) frames_shown = 0;
748                                 if (frames_shown > frames_done) frames_shown = frames_done;
749                         } else {
750                                 frames_done = frames_shown = 0;
751                         }
752                 }
753 #ifdef PFRAMES
754                 sprintf(fpsbuff, "%i", Pico.m.frame_count);
755 #endif
756
757                 tval_prev = tval;
758                 lim_time = (frames_done+1) * target_frametime;
759                 if (currentConfig.Frameskip >= 0) // frameskip enabled
760                 {
761                         for (i = 0; i < currentConfig.Frameskip; i++) {
762                                 updateKeys();
763                                 SkipFrame(); frames_done++;
764                                 if (PsndOut) { // do framelimitting if sound is enabled
765                                         int tval_diff;
766                                         tval = sceKernelGetSystemTimeLow();
767                                         tval_diff = (int)(tval - tval_thissec) << 8;
768                                         if (tval_diff < lim_time) // we are too fast
769                                                 simpleWait(tval + ((lim_time - tval_diff)>>8));
770                                 }
771                                 lim_time += target_frametime;
772                         }
773                 }
774                 else // auto frameskip
775                 {
776                         int tval_diff;
777                         tval = sceKernelGetSystemTimeLow();
778                         tval_diff = (int)(tval - tval_thissec) << 8;
779                         if (tval_diff > lim_time)
780                         {
781                                 // no time left for this frame - skip
782                                 if (tval_diff - lim_time >= (300000<<8)) {
783                                         /* something caused a slowdown for us (disk access? cache flush?)
784                                          * try to recover by resetting timing... */
785                                         reset_timing = 1;
786                                         continue;
787                                 }
788                                 updateKeys();
789                                 SkipFrame(); frames_done++;
790                                 continue;
791                         }
792                 }
793
794                 updateKeys();
795
796                 if (!(PicoOpt&0x10))
797                         EmuScanPrepare();
798
799                 PicoFrame();
800
801                 blit(fpsbuff, notice);
802
803                 // check time
804                 tval = sceKernelGetSystemTimeLow();
805                 tval_diff = (int)(tval - tval_thissec) << 8;
806
807                 if (currentConfig.Frameskip < 0 && tval_diff - lim_time >= (300000<<8)) // slowdown detection
808                         reset_timing = 1;
809                 else if (PsndOut != NULL || currentConfig.Frameskip < 0)
810                 {
811                         // sleep if we are still too fast
812                         if (tval_diff < lim_time)
813                         {
814                                 // we are too fast
815                                 simpleWait(tval + ((lim_time - tval_diff) >> 8));
816                         }
817                 }
818
819                 frames_done++; frames_shown++;
820         }
821
822
823         if (PicoMCD & 1) PicoCDBufferFree();
824 /*
825         if (PsndOut != NULL) {
826                 PsndOut = snd_cbuff = NULL;
827                 FrameworkAudio_Close();
828         }
829 */
830         // save SRAM
831         if ((currentConfig.EmuOpt & 1) && SRam.changed) {
832                 emu_stateCb("Writing SRAM/BRAM..");
833                 emu_SaveLoadGame(0, 1);
834                 SRam.changed = 0;
835         }
836 }
837
838
839 void emu_ResetGame(void)
840 {
841         PicoReset(0);
842         reset_timing = 1;
843 }
844