psp port runs, bad colors
[libpicofe.git] / psp / menu.c
1 // (c) Copyright 2006,2007 notaz, All rights reserved.
2 // Free for non-commercial use.
3
4 // For commercial use, separate licencing terms must be obtained.
5
6 // don't like to use loads of #ifdefs, so duplicating GP2X code
7 // horribly instead
8
9 //#include <stdio.h>
10 #include <string.h>
11 #include <stdlib.h>
12 #include <wchar.h>
13 #include <unistd.h>
14 #include <sys/syslimits.h> // PATH_MAX
15
16 #include <pspdisplay.h>
17 #include <pspgu.h>
18 #include <pspiofilemgr.h>
19
20 #include "psp.h"
21 #include "emu.h"
22 #include "menu.h"
23 #include "../common/menu.h"
24 #include "../common/emu.h"
25 #include "../common/readpng.h"
26 #include "../common/lprintf.h"
27 #include "version.h"
28
29 #include <Pico/PicoInt.h>
30 #include <Pico/Patch.h>
31 #include <zlib/zlib.h>
32
33
34 #define pspKeyUnkn "???"
35 static const char * const pspKeyNames[] = {
36         "SELECT",   pspKeyUnkn, pspKeyUnkn, "START",    "UP",       "RIGHT",    "DOWN",     "LEFT",
37         "L",        "R",        pspKeyUnkn, pspKeyUnkn, "TRIANGLE", "CIRCLE",   "X",        "SQUARE",
38         "HOME",     "HOLD",     "WLAN_UP",  "REMOTE",   "VOLUP",    "VOLDOWN",  "SCREEN",   "NOTE",
39         pspKeyUnkn, pspKeyUnkn, pspKeyUnkn, pspKeyUnkn, pspKeyUnkn, pspKeyUnkn, pspKeyUnkn, pspKeyUnkn
40 };
41
42 unsigned int __attribute__((aligned(16))) guCmdList[1024]; // TODO: adjust, mv
43
44 static unsigned char bg_buffer[480*272*2] __attribute__((aligned(16))); // TODO: move to vram?
45 #define menu_screen psp_screen
46
47 static void menu_darken_bg(void *dst, const void *src, int pixels, int darker);
48 static void menu_prepare_bg(int use_game_bg);
49
50
51 static unsigned int inp_prev = 0;
52
53 static unsigned long wait_for_input(unsigned int interesting)
54 {
55         unsigned int ret;
56         static int repeats = 0, wait = 50;
57         int release = 0, i;
58
59         if (repeats == 2 || repeats == 4) wait /= 2;
60         if (repeats == 6) wait = 15;
61
62         for (i = 0; i < 6 && inp_prev == psp_pad_read(1); i++) {
63                 if (i == 0) repeats++;
64                 psp_msleep(wait);
65         }
66
67         while ( !((ret = psp_pad_read(1)) & interesting) ) {
68                 psp_msleep(50);
69                 release = 1;
70         }
71
72         if (release || ret != inp_prev) {
73                 repeats = 0;
74                 wait = 50;
75         }
76         inp_prev = ret;
77
78         // we don't need diagonals in menus
79         if ((ret&BTN_UP)   && (ret&BTN_LEFT))  ret &= ~BTN_LEFT;
80         if ((ret&BTN_UP)   && (ret&BTN_RIGHT)) ret &= ~BTN_RIGHT;
81         if ((ret&BTN_DOWN) && (ret&BTN_LEFT))  ret &= ~BTN_LEFT;
82         if ((ret&BTN_DOWN) && (ret&BTN_RIGHT)) ret &= ~BTN_RIGHT;
83
84         return ret;
85 }
86
87 static void menu_draw_begin(void)
88 {
89         // short *src = (short *)bg_buffer, *dst = (short *)menu_screen;
90         // int i;
91
92         // for (i = 272; i >= 0; i--, dst += 512, src += 480)
93         //      memcpy32((int *)dst, (int *)src, 480*2/4);
94
95         sceGuStart(GU_DIRECT, guCmdList);
96         sceGuCopyImage(GU_PSM_5650, 0, 0, 480, 272, 480, bg_buffer, 0, 0, 512, menu_screen);
97         sceGuFinish();
98         sceGuSync(0, GU_SYNC_FINISH);
99 }
100
101
102 static void menu_draw_end(void)
103 {
104         psp_video_flip(1);
105 }
106
107
108 // --------- loading ROM screen ----------
109
110 static void load_progress_cb(int percent)
111 {
112         int ln, len = percent * 480 / 100;
113         unsigned short *dst;
114
115         sceDisplayWaitVblankStart();
116
117         dst = (unsigned short *)menu_screen + 512*20;
118
119         if (len > 480) len = 480;
120         for (ln = 10; ln > 0; ln--, dst += 512)
121                 memset(dst, 0xff, len*2);
122 }
123
124 void menu_romload_prepare(const char *rom_name)
125 {
126         const char *p = rom_name + strlen(rom_name);
127         while (p > rom_name && *p != '/') p--;
128
129         psp_video_switch_to_single();
130         menu_draw_begin();
131
132         smalltext_out16(1, 1, "Loading", 0xffff);
133         smalltext_out16_lim(1, 10, p, 0xffff, 80);
134         PicoCartLoadProgressCB = load_progress_cb;
135 }
136
137 void menu_romload_end(void)
138 {
139         PicoCartLoadProgressCB = NULL;
140         smalltext_out16(1, 30, "Starting emulation...", 0xffff);
141 }
142
143 // -------------- ROM selector --------------
144
145 // SceIoDirent
146 #define DT_DIR FIO_SO_IFDIR
147 #define DT_REG FIO_SO_IFREG
148
149 struct my_dirent
150 {
151         unsigned char d_type;
152         char d_name[255];
153 };
154
155 // bbbb bggg gggr rrrr
156 static unsigned short file2color(const char *fname)
157 {
158         const char *ext = fname + strlen(fname) - 3;
159         static const char *rom_exts[]   = { "zip", "bin", "smd", "gen", "iso" };
160         static const char *other_exts[] = { "gmv", "pat" };
161         int i;
162
163         if (ext < fname) ext = fname;
164         for (i = 0; i < sizeof(rom_exts)/sizeof(rom_exts[0]); i++)
165                 if (strcasecmp(ext, rom_exts[i]) == 0) return 0xfdf7;
166         for (i = 0; i < sizeof(other_exts)/sizeof(other_exts[0]); i++)
167                 if (strcasecmp(ext, other_exts[i]) == 0) return 0xaff5;
168         return 0xffff;
169 }
170
171 static void draw_dirlist(char *curdir, struct my_dirent **namelist, int n, int sel)
172 {
173         int start, i, pos;
174
175         start = 13 - sel;
176         n--; // exclude current dir (".")
177
178         menu_draw_begin();
179
180         if (rom_data == NULL) {
181 //              menu_darken_bg(menu_screen, menu_screen, 321*240, 0);
182         }
183
184         menu_darken_bg((char *)menu_screen + 512*129*2, (char *)menu_screen + 512*129*2, 512*10, 0);
185
186         if (start - 2 >= 0)
187                 smalltext_out16_lim(14, (start - 2)*10, curdir, 0xffff, 53-2);
188         for (i = 0; i < n; i++) {
189                 pos = start + i;
190                 if (pos < 0)  continue;
191                 if (pos > 26) break;
192                 if (namelist[i+1]->d_type & DT_DIR) {
193                         smalltext_out16_lim(14,   pos*10, "/", 0xd7ff, 1);
194                         smalltext_out16_lim(14+6, pos*10, namelist[i+1]->d_name, 0xd7ff, 53-3);
195                 } else {
196                         unsigned short color = file2color(namelist[i+1]->d_name);
197                         smalltext_out16_lim(14,   pos*10, namelist[i+1]->d_name, color, 53-2);
198                 }
199         }
200         text_out16(5, 130, ">");
201         menu_draw_end();
202 }
203
204 static int scandir_cmp(const void *p1, const void *p2)
205 {
206         struct my_dirent **d1 = (struct my_dirent **)p1, **d2 = (struct my_dirent **)p2;
207         if ((*d1)->d_type == (*d2)->d_type) return strcasecmp((*d1)->d_name, (*d2)->d_name);
208         if ((*d1)->d_type & DT_DIR) return -1; // put before
209         if ((*d2)->d_type & DT_DIR) return  1;
210         return strcasecmp((*d1)->d_name, (*d2)->d_name);
211 }
212
213 static char *filter_exts[] = {
214         ".mp3", ".srm", ".brm", "s.gz", ".mds", "bcfg", ".txt", ".htm", "html",
215         ".jpg", ".cue", ".pbp"
216 };
217
218 static int scandir_filter(const struct my_dirent *ent)
219 {
220         const char *p;
221         int i;
222
223         if (ent == NULL || ent->d_name == NULL) return 0;
224         if (strlen(ent->d_name) < 5) return 1;
225
226         p = ent->d_name + strlen(ent->d_name) - 4;
227
228         for (i = 0; i < sizeof(filter_exts)/sizeof(filter_exts[0]); i++)
229         {
230                 if (strcasecmp(p, filter_exts[i]) == 0) return 0;
231         }
232
233         return 1;
234 }
235
236 static int my_scandir(const char *dir, struct my_dirent ***namelist_out,
237                 int(*filter)(const struct my_dirent *),
238                 int(*compar)(const void *, const void *))
239 {
240         int ret = -1, dir_uid = -1, name_alloc = 4, name_count = 0;
241         struct my_dirent **namelist = NULL, *ent;
242         SceIoDirent sce_ent;
243
244         namelist = malloc(sizeof(*namelist) * name_alloc);
245         if (namelist == NULL) { lprintf("%s:%i: OOM\n", __FILE__, __LINE__); goto fail; }
246
247         // try to read first..
248         dir_uid = sceIoDopen(dir);
249         if (dir_uid >= 0)
250         {
251                 /* it is very important to clear SceIoDirent to be passed to sceIoDread(), */
252                 /* or else it may crash, probably misinterpreting something in it. */
253                 memset(&sce_ent, 0, sizeof(sce_ent));
254                 ret = sceIoDread(dir_uid, &sce_ent);
255                 if (ret < 0)
256                 {
257                         lprintf("sceIoDread(\"%s\") failed with %i\n", dir, ret);
258                         goto fail;
259                 }
260         }
261         else
262                 lprintf("sceIoDopen(\"%s\") failed with %i\n", dir, dir_uid);
263
264         while (ret > 0)
265         {
266                 ent = malloc(sizeof(*ent));
267                 if (ent == NULL) { lprintf("%s:%i: OOM\n", __FILE__, __LINE__); goto fail; }
268                 ent->d_type = sce_ent.d_stat.st_attr;
269                 strncpy(ent->d_name, sce_ent.d_name, sizeof(ent->d_name));
270                 ent->d_name[sizeof(ent->d_name)-1] = 0;
271                 if (filter == NULL || filter(ent))
272                      namelist[name_count++] = ent;
273                 else free(ent);
274
275                 if (name_count >= name_alloc)
276                 {
277                         void *tmp;
278                         name_alloc *= 2;
279                         tmp = realloc(namelist, sizeof(*namelist) * name_alloc);
280                         if (tmp == NULL) { lprintf("%s:%i: OOM\n", __FILE__, __LINE__); goto fail; }
281                         namelist = tmp;
282                 }
283
284                 memset(&sce_ent, 0, sizeof(sce_ent));
285                 ret = sceIoDread(dir_uid, &sce_ent);
286         }
287
288         // sort
289         if (compar != NULL && name_count > 3) qsort(&namelist[2], name_count - 2, sizeof(namelist[0]), compar);
290
291         // all done.
292         ret = name_count;
293         *namelist_out = namelist;
294         goto end;
295
296 fail:
297         if (namelist != NULL)
298         {
299                 while (name_count--)
300                         free(namelist[name_count]);
301                 free(namelist);
302         }
303 end:
304         if (dir_uid >= 0) sceIoDclose(dir_uid);
305         return ret;
306 }
307
308
309 static char *romsel_loop(char *curr_path)
310 {
311         struct my_dirent **namelist;
312         int n, iret, sel = 0;
313         unsigned long inp = 0;
314         char *ret = NULL, *fname = NULL;
315         SceIoStat cpstat;
316
317         // is this a dir or a full path?
318         memset(&cpstat, 0, sizeof(cpstat));
319         iret = sceIoGetstat(curr_path, &cpstat);
320         if (iret >= 0 && (cpstat.st_attr & FIO_SO_IFREG)) { // file
321                 char *p;
322                 for (p = curr_path + strlen(curr_path) - 1; p > curr_path && *p != '/'; p--);
323                 *p = 0;
324                 fname = p+1;
325         }
326         else if (iret >= 0 && (cpstat.st_attr & FIO_SO_IFDIR)); // dir
327         else strcpy(curr_path, "ms0:/"); // something else
328
329         n = my_scandir(curr_path, &namelist, scandir_filter, scandir_cmp);
330         if (n < 0) {
331                 // try root..
332                 n = my_scandir("ms0:/", &namelist, scandir_filter, scandir_cmp);
333                 if (n < 0) {
334                         // oops, we failed
335                         lprintf("scandir failed, dir: "); lprintf(curr_path); lprintf("\n");
336                         return NULL;
337                 }
338         }
339
340         // try to find sel
341         if (fname != NULL) {
342                 int i;
343                 for (i = 1; i < n; i++) {
344                         if (strcmp(namelist[i]->d_name, fname) == 0) {
345                                 sel = i - 1;
346                                 break;
347                         }
348                 }
349         }
350
351         for (;;)
352         {
353                 draw_dirlist(curr_path, namelist, n, sel);
354                 inp = wait_for_input(BTN_UP|BTN_DOWN|BTN_LEFT|BTN_RIGHT|BTN_L|BTN_R|BTN_X|BTN_CIRCLE);
355                 if(inp & BTN_UP  )  { sel--;   if (sel < 0)   sel = n-2; }
356                 if(inp & BTN_DOWN)  { sel++;   if (sel > n-2) sel = 0; }
357                 if(inp & BTN_LEFT)  { sel-=10; if (sel < 0)   sel = 0; }
358                 if(inp & BTN_L)     { sel-=24; if (sel < 0)   sel = 0; }
359                 if(inp & BTN_RIGHT) { sel+=10; if (sel > n-2) sel = n-2; }
360                 if(inp & BTN_R)     { sel+=24; if (sel > n-2) sel = n-2; }
361                 if(inp & BTN_X)     { // enter dir/select
362                         if (namelist[sel+1]->d_type & DT_REG) {
363                                 strcpy(romFileName, curr_path);
364                                 strcat(romFileName, "/");
365                                 strcat(romFileName, namelist[sel+1]->d_name);
366                                 ret = romFileName;
367                                 break;
368                         } else if (namelist[sel+1]->d_type & DT_DIR) {
369                                 int newlen = strlen(curr_path) + strlen(namelist[sel+1]->d_name) + 2;
370                                 char *p, *newdir = malloc(newlen);
371                                 if (strcmp(namelist[sel+1]->d_name, "..") == 0) {
372                                         char *start = curr_path;
373                                         p = start + strlen(start) - 1;
374                                         while (*p == '/' && p > start) p--;
375                                         while (*p != '/' && *p != ':' && p > start) p--;
376                                         if (p <= start || *p == ':' || p[-1] == ':') strcpy(newdir, "ms0:/");
377                                         else { strncpy(newdir, start, p-start); newdir[p-start] = 0; }
378                                 } else {
379                                         strcpy(newdir, curr_path);
380                                         p = newdir + strlen(newdir) - 1;
381                                         while (*p == '/' && p >= newdir) *p-- = 0;
382                                         strcat(newdir, "/");
383                                         strcat(newdir, namelist[sel+1]->d_name);
384                                 }
385                                 ret = romsel_loop(newdir);
386                                 free(newdir);
387                                 break;
388                         }
389                 }
390                 if(inp & BTN_CIRCLE) break; // cancel
391         }
392
393         if (n > 0) {
394                 while(n--) free(namelist[n]);
395                 free(namelist);
396         }
397
398         return ret;
399 }
400
401 // ------------ debug menu ------------
402
403 char *debugString(void);
404
405 static void draw_debug(void)
406 {
407         char *p, *str = debugString();
408         int len, line;
409
410         menu_draw_begin();
411
412         p = str;
413         for (line = 0; line < 24; line++)
414         {
415                 while (*p && *p != '\n') p++;
416                 len = p - str;
417                 if (len > 55) len = 55;
418                 smalltext_out16_lim(1, line*10, str, 0xffff, len);
419                 if (*p == 0) break;
420                 p++; str = p;
421         }
422         menu_draw_end();
423 }
424
425 static void debug_menu_loop(void)
426 {
427         draw_debug();
428         wait_for_input(BTN_X|BTN_CIRCLE);
429 }
430
431 // ------------ patch/gg menu ------------
432
433 static void draw_patchlist(int sel)
434 {
435         int start, i, pos, active;
436
437         start = 13 - sel;
438
439         menu_draw_begin();
440
441         for (i = 0; i < PicoPatchCount; i++) {
442                 pos = start + i;
443                 if (pos < 0)  continue;
444                 if (pos > 26) break;
445                 active = PicoPatches[i].active;
446                 smalltext_out16_lim(14,     pos*10, active ? "ON " : "OFF", active ? 0xfff6 : 0xffff, 3);
447                 smalltext_out16_lim(14+6*4, pos*10, PicoPatches[i].name, active ? 0xfff6 : 0xffff, 53-6);
448         }
449         pos = start + i;
450         if (pos < 27) smalltext_out16_lim(14, pos*10, "done", 0xffff, 4);
451
452         text_out16(5, 130, ">");
453         menu_draw_end();
454 }
455
456
457 static void patches_menu_loop(void)
458 {
459         int menu_sel = 0;
460         unsigned long inp = 0;
461
462         for(;;)
463         {
464                 draw_patchlist(menu_sel);
465                 inp = wait_for_input(BTN_UP|BTN_DOWN|BTN_LEFT|BTN_RIGHT|BTN_L|BTN_R|BTN_X|BTN_CIRCLE);
466                 if(inp & BTN_UP  ) { menu_sel--; if (menu_sel < 0) menu_sel = PicoPatchCount; }
467                 if(inp & BTN_DOWN) { menu_sel++; if (menu_sel > PicoPatchCount) menu_sel = 0; }
468                 if(inp &(BTN_LEFT|BTN_L))  { menu_sel-=10; if (menu_sel < 0) menu_sel = 0; }
469                 if(inp &(BTN_RIGHT|BTN_R)) { menu_sel+=10; if (menu_sel > PicoPatchCount) menu_sel = PicoPatchCount; }
470                 if(inp & BTN_X) { // action
471                         if (menu_sel < PicoPatchCount)
472                                 PicoPatches[menu_sel].active = !PicoPatches[menu_sel].active;
473                         else    return;
474                 }
475                 if(inp & BTN_CIRCLE) return;
476         }
477
478 }
479
480 // ------------ savestate loader ------------
481
482 static int state_slot_flags = 0;
483
484 static void state_check_slots(void)
485 {
486         int slot;
487
488         state_slot_flags = 0;
489
490         for (slot = 0; slot < 10; slot++)
491         {
492                 if (emu_checkSaveFile(slot))
493                 {
494                         state_slot_flags |= 1 << slot;
495                 }
496         }
497 }
498
499 static void draw_savestate_bg(int slot)
500 {
501         struct PicoVideo tmp_pv;
502         unsigned short tmp_cram[0x40];
503         unsigned short tmp_vsram[0x40];
504         void *tmp_vram, *file;
505         char *fname;
506
507         fname = emu_GetSaveFName(1, 0, slot);
508         if (!fname) return;
509
510         tmp_vram = malloc(sizeof(Pico.vram));
511         if (tmp_vram == NULL) return;
512
513         memcpy(tmp_vram, Pico.vram, sizeof(Pico.vram));
514         memcpy(tmp_cram, Pico.cram, sizeof(Pico.cram));
515         memcpy(tmp_vsram, Pico.vsram, sizeof(Pico.vsram));
516         memcpy(&tmp_pv, &Pico.video, sizeof(Pico.video));
517
518         if (strcmp(fname + strlen(fname) - 3, ".gz") == 0) {
519                 file = gzopen(fname, "rb");
520                 emu_setSaveStateCbs(1);
521         } else {
522                 file = fopen(fname, "rb");
523                 emu_setSaveStateCbs(0);
524         }
525
526         if (file) {
527                 if (PicoMCD & 1) {
528                         PicoCdLoadStateGfx(file);
529                 } else {
530                         areaSeek(file, 0x10020, SEEK_SET);  // skip header and RAM in state file
531                         areaRead(Pico.vram, 1, sizeof(Pico.vram), file);
532                         areaSeek(file, 0x2000, SEEK_CUR);
533                         areaRead(Pico.cram, 1, sizeof(Pico.cram), file);
534                         areaRead(Pico.vsram, 1, sizeof(Pico.vsram), file);
535                         areaSeek(file, 0x221a0, SEEK_SET);
536                         areaRead(&Pico.video, 1, sizeof(Pico.video), file);
537                 }
538                 areaClose(file);
539         }
540
541         emu_forcedFrame();
542         menu_prepare_bg(1);
543
544         memcpy(Pico.vram, tmp_vram, sizeof(Pico.vram));
545         memcpy(Pico.cram, tmp_cram, sizeof(Pico.cram));
546         memcpy(Pico.vsram, tmp_vsram, sizeof(Pico.vsram));
547         memcpy(&Pico.video, &tmp_pv,  sizeof(Pico.video));
548         free(tmp_vram);
549 }
550
551 static void draw_savestate_menu(int menu_sel, int is_loading)
552 {
553         int tl_x = 80+25, tl_y = 16+60, y, i;
554
555         if (state_slot_flags & (1 << menu_sel))
556                 draw_savestate_bg(menu_sel);
557         menu_draw_begin();
558
559         text_out16(tl_x, 16+30, is_loading ? "Load state" : "Save state");
560
561         menu_draw_selection(tl_x - 16, tl_y + menu_sel*10, 108);
562
563         /* draw all 10 slots */
564         y = tl_y;
565         for (i = 0; i < 10; i++, y+=10)
566         {
567                 text_out16(tl_x, y, "SLOT %i (%s)", i, (state_slot_flags & (1 << i)) ? "USED" : "free");
568         }
569         text_out16(tl_x, y, "back");
570
571         menu_draw_end();
572 }
573
574 static int savestate_menu_loop(int is_loading)
575 {
576         static int menu_sel = 10;
577         int menu_sel_max = 10;
578         unsigned long inp = 0;
579
580         state_check_slots();
581
582         for(;;)
583         {
584                 draw_savestate_menu(menu_sel, is_loading);
585                 inp = wait_for_input(BTN_UP|BTN_DOWN|BTN_X|BTN_CIRCLE);
586                 if(inp & BTN_UP  ) {
587                         do {
588                                 menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max;
589                         } while (!(state_slot_flags & (1 << menu_sel)) && menu_sel != menu_sel_max && is_loading);
590                 }
591                 if(inp & BTN_DOWN) {
592                         do {
593                                 menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0;
594                         } while (!(state_slot_flags & (1 << menu_sel)) && menu_sel != menu_sel_max && is_loading);
595                 }
596                 if(inp & BTN_X) { // save/load
597                         if (menu_sel < 10) {
598                                 state_slot = menu_sel;
599                                 PicoStateProgressCB = emu_stateCb; /* also suitable for menu */
600                                 if (emu_SaveLoadGame(is_loading, 0)) {
601                                         strcpy(menuErrorMsg, is_loading ? "Load failed" : "Save failed");
602                                         return 1;
603                                 }
604                                 return 0;
605                         } else  return 1;
606                 }
607                 if(inp & BTN_CIRCLE) return 1;
608         }
609 }
610
611 // -------------- key config --------------
612
613 static char *action_binds(int player_idx, int action_mask)
614 {
615         static char strkeys[32*5];
616         int i;
617
618         strkeys[0] = 0;
619         for (i = 0; i < 32; i++) // i is key index
620         {
621                 if (currentConfig.KeyBinds[i] & action_mask)
622                 {
623                         if (player_idx >= 0 && ((currentConfig.KeyBinds[i] >> 16) & 3) != player_idx) continue;
624                         if (strkeys[0]) { strcat(strkeys, " + "); strcat(strkeys, pspKeyNames[i]); break; }
625                         else strcpy(strkeys, pspKeyNames[i]);
626                 }
627         }
628
629         return strkeys;
630 }
631
632 static void unbind_action(int action)
633 {
634         int i;
635
636         for (i = 0; i < 32; i++)
637                 currentConfig.KeyBinds[i] &= ~action;
638 }
639
640 static int count_bound_keys(int action, int pl_idx)
641 {
642         int i, keys = 0;
643
644         for (i = 0; i < 32; i++)
645         {
646                 if (pl_idx >= 0 && (currentConfig.KeyBinds[i]&0x30000) != (pl_idx<<16)) continue;
647                 if (currentConfig.KeyBinds[i] & action) keys++;
648         }
649
650         return keys;
651 }
652
653 typedef struct { char *name; int mask; } bind_action_t;
654
655 static void draw_key_config(const bind_action_t *opts, int opt_cnt, int player_idx, int sel)
656 {
657         int x, y, tl_y = 16+40, i;
658
659         menu_draw_begin();
660         if (player_idx >= 0) {
661                 text_out16(80+80, 16+20, "Player %i controls", player_idx + 1);
662                 x = 80+80;
663         } else {
664                 text_out16(80+80, 16+20, "Emulator controls");
665                 x = 80+40;
666         }
667
668         menu_draw_selection(x - 16, tl_y + sel*10, (player_idx >= 0) ? 66 : 130);
669
670         y = tl_y;
671         for (i = 0; i < opt_cnt; i++, y+=10)
672                 text_out16(x, y, "%s : %s", opts[i].name, action_binds(player_idx, opts[i].mask));
673
674         text_out16(x, y, "Done");
675
676         if (sel < opt_cnt) {
677                 text_out16(80+30, 180, "Press a button to bind/unbind");
678                 text_out16(80+30, 190, "Use SELECT to clear");
679                 text_out16(80+30, 200, "To bind UP/DOWN, hold SELECT");
680                 text_out16(80+30, 210, "Select \"Done\" to exit");
681         } else {
682                 text_out16(80+30, 190, "Use Options -> Save cfg");
683                 text_out16(80+30, 200, "to save controls");
684                 text_out16(80+30, 210, "Press X or O to exit");
685         }
686         menu_draw_end();
687 }
688
689 static void key_config_loop(const bind_action_t *opts, int opt_cnt, int player_idx)
690 {
691         int sel = 0, menu_sel_max = opt_cnt, prev_select = 0, i;
692         unsigned long inp = 0;
693
694         for (;;)
695         {
696                 draw_key_config(opts, opt_cnt, player_idx, sel);
697                 inp = wait_for_input(CONFIGURABLE_KEYS|BTN_SELECT);
698                 if (!(inp & BTN_SELECT)) {
699                         prev_select = 0;
700                         if(inp & BTN_UP  ) { sel--; if (sel < 0) sel = menu_sel_max; continue; }
701                         if(inp & BTN_DOWN) { sel++; if (sel > menu_sel_max) sel = 0; continue; }
702                 }
703                 if (sel >= opt_cnt) {
704                         if (inp & (BTN_X|BTN_CIRCLE)) break;
705                         else continue;
706                 }
707                 // if we are here, we want to bind/unbind something
708                 if ((inp & BTN_SELECT) && !prev_select)
709                         unbind_action(opts[sel].mask);
710                 prev_select = inp & BTN_SELECT;
711                 inp &= CONFIGURABLE_KEYS;
712                 inp &= ~BTN_SELECT;
713                 for (i = 0; i < 32; i++)
714                         if (inp & (1 << i)) {
715                                 if (count_bound_keys(opts[sel].mask, player_idx) >= 2)
716                                      currentConfig.KeyBinds[i] &= ~opts[sel].mask; // allow to unbind only
717                                 else currentConfig.KeyBinds[i] ^=  opts[sel].mask;
718                                 if (player_idx >= 0 && (currentConfig.KeyBinds[i] & opts[sel].mask)) {
719                                         currentConfig.KeyBinds[i] &= ~(3 << 16);
720                                         currentConfig.KeyBinds[i] |= player_idx << 16;
721                                 }
722                         }
723         }
724 }
725
726 static void draw_kc_sel(int menu_sel)
727 {
728         int tl_x = 80+25+40, tl_y = 16+60, y;
729
730         y = tl_y;
731         menu_draw_begin();
732         menu_draw_selection(tl_x - 16, tl_y + menu_sel*10, 138);
733
734         text_out16(tl_x, y,       "Player 1");
735         text_out16(tl_x, (y+=10), "Player 2");
736         text_out16(tl_x, (y+=10), "Emulator controls");
737         text_out16(tl_x, (y+=10), "Done");
738
739         menu_draw_end();
740 }
741
742
743 // PicoPad[] format: MXYZ SACB RLDU
744 static bind_action_t ctrl_actions[] =
745 {
746         { "UP     ", 0x001 },
747         { "DOWN   ", 0x002 },
748         { "LEFT   ", 0x004 },
749         { "RIGHT  ", 0x008 },
750         { "A      ", 0x040 },
751         { "B      ", 0x010 },
752         { "C      ", 0x020 },
753         { "START  ", 0x080 },
754         { "MODE   ", 0x800 },
755         { "X      ", 0x400 },
756         { "Y      ", 0x200 },
757         { "Z      ", 0x100 },
758 };
759
760 // player2_flag, ?, ?, ?, ?, ?, ?, menu
761 // "NEXT SAVE SLOT", "PREV SAVE SLOT", "SWITCH RENDERER", "SAVE STATE",
762 // "LOAD STATE", "VOLUME UP", "VOLUME DOWN", "DONE"
763 static bind_action_t emuctrl_actions[] =
764 {
765         { "Load State     ", 1<<28 },
766         { "Save State     ", 1<<27 },
767         { "Prev Save Slot ", 1<<25 },
768         { "Next Save Slot ", 1<<24 },
769         { "Switch Renderer", 1<<26 },
770         { "Volume Down    ", 1<<30 },
771         { "Volume Up      ", 1<<29 },
772 };
773
774 static void kc_sel_loop(void)
775 {
776         int menu_sel = 3, menu_sel_max = 3;
777         unsigned long inp = 0;
778         int is_6button = currentConfig.PicoOpt & 0x020;
779
780         while (1)
781         {
782                 draw_kc_sel(menu_sel);
783                 inp = wait_for_input(BTN_UP|BTN_DOWN|BTN_X|BTN_CIRCLE);
784                 if (inp & BTN_UP  ) { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; }
785                 if (inp & BTN_DOWN) { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; }
786                 if (inp & BTN_X) {
787                         switch (menu_sel) {
788                                 case 0: key_config_loop(ctrl_actions, is_6button ? 12 : 8, 0); return;
789                                 case 1: key_config_loop(ctrl_actions, is_6button ? 12 : 8, 1); return;
790                                 case 2: key_config_loop(emuctrl_actions,
791                                                 sizeof(emuctrl_actions)/sizeof(emuctrl_actions[0]), -1); return;
792                                 case 3: if (rom_data == NULL) emu_WriteConfig(0); return;
793                                 default: return;
794                         }
795                 }
796                 if (inp & BTN_CIRCLE) return;
797         }
798 }
799
800
801 // --------- sega/mega cd options ----------
802
803 menu_entry cdopt_entries[] =
804 {
805         { NULL,                        MB_NONE,  MA_CDOPT_TESTBIOS_USA, NULL, 0, 0, 0, 1 },
806         { NULL,                        MB_NONE,  MA_CDOPT_TESTBIOS_EUR, NULL, 0, 0, 0, 1 },
807         { NULL,                        MB_NONE,  MA_CDOPT_TESTBIOS_JAP, NULL, 0, 0, 0, 1 },
808         { "CD LEDs",                   MB_ONOFF, MA_CDOPT_LEDS,         &currentConfig.EmuOpt,  0x0400, 0, 0, 1 },
809         { "CDDA audio (using mp3s)",   MB_ONOFF, MA_CDOPT_CDDA,         &currentConfig.PicoOpt, 0x0800, 0, 0, 1 },
810         { "PCM audio",                 MB_ONOFF, MA_CDOPT_PCM,          &currentConfig.PicoOpt, 0x0400, 0, 0, 1 },
811         { NULL,                        MB_NONE,  MA_CDOPT_READAHEAD,    NULL, 0, 0, 0, 1 },
812         { "SaveRAM cart",              MB_ONOFF, MA_CDOPT_SAVERAM,      &currentConfig.PicoOpt, 0x8000, 0, 0, 1 },
813         { "Scale/Rot. fx (slow)",      MB_ONOFF, MA_CDOPT_SCALEROT_CHIP,&currentConfig.PicoOpt, 0x1000, 0, 0, 1 },
814         { "Better sync (slow)",        MB_ONOFF, MA_CDOPT_BETTER_SYNC,  &currentConfig.PicoOpt, 0x2000, 0, 0, 1 },
815         { "done",                      MB_NONE,  MA_CDOPT_DONE,         NULL, 0, 0, 0, 1 },
816 };
817
818 #define CDOPT_ENTRY_COUNT (sizeof(cdopt_entries) / sizeof(cdopt_entries[0]))
819
820
821 struct bios_names_t
822 {
823         char us[32], eu[32], jp[32];
824 };
825
826 static void menu_cdopt_cust_draw(const menu_entry *entry, int x, int y, void *param)
827 {
828         struct bios_names_t *bios_names = param;
829         char ra_buff[16];
830
831         switch (entry->id)
832         {
833                 case MA_CDOPT_TESTBIOS_USA: text_out16(x, y, "USA BIOS:     %s", bios_names->us); break;
834                 case MA_CDOPT_TESTBIOS_EUR: text_out16(x, y, "EUR BIOS:     %s", bios_names->eu); break;
835                 case MA_CDOPT_TESTBIOS_JAP: text_out16(x, y, "JAP BIOS:     %s", bios_names->jp); break;
836                 case MA_CDOPT_READAHEAD:
837                         if (PicoCDBuffers > 1) sprintf(ra_buff, "%5iK", PicoCDBuffers * 2);
838                         else strcpy(ra_buff, "     OFF");
839                         text_out16(x, y, "ReadAhead buffer      %s", ra_buff);
840                         break;
841                 default:break;
842         }
843 }
844
845 static void draw_cd_menu_options(int menu_sel, struct bios_names_t *bios_names)
846 {
847         int tl_x = 80+25, tl_y = 16+60;
848         menu_id selected_id;
849         char ra_buff[16];
850
851         if (PicoCDBuffers > 1) sprintf(ra_buff, "%5iK", PicoCDBuffers * 2);
852         else strcpy(ra_buff, "     OFF");
853
854         menu_draw_begin();
855
856         menu_draw_selection(tl_x - 16, tl_y + menu_sel*10, 246);
857
858         me_draw(cdopt_entries, CDOPT_ENTRY_COUNT, tl_x, tl_y, menu_cdopt_cust_draw, bios_names);
859
860         selected_id = me_index2id(cdopt_entries, CDOPT_ENTRY_COUNT, menu_sel);
861         if ((selected_id == MA_CDOPT_TESTBIOS_USA && strcmp(bios_names->us, "NOT FOUND")) ||
862                 (selected_id == MA_CDOPT_TESTBIOS_EUR && strcmp(bios_names->eu, "NOT FOUND")) ||
863                 (selected_id == MA_CDOPT_TESTBIOS_JAP && strcmp(bios_names->jp, "NOT FOUND")))
864                         text_out16(tl_x, 210, "Press start to test selected BIOS");
865
866         menu_draw_end();
867 }
868
869 static void cd_menu_loop_options(void)
870 {
871         static int menu_sel = 0;
872         int menu_sel_max = 10;
873         unsigned long inp = 0;
874         struct bios_names_t bios_names;
875         menu_id selected_id;
876         char *bios, *p;
877
878         if (emu_findBios(4, &bios)) { // US
879                 for (p = bios+strlen(bios)-1; p > bios && *p != '/'; p--); p++;
880                 strncpy(bios_names.us, p, sizeof(bios_names.us)); bios_names.us[sizeof(bios_names.us)-1] = 0;
881         } else  strcpy(bios_names.us, "NOT FOUND");
882
883         if (emu_findBios(8, &bios)) { // EU
884                 for (p = bios+strlen(bios)-1; p > bios && *p != '/'; p--); p++;
885                 strncpy(bios_names.eu, p, sizeof(bios_names.eu)); bios_names.eu[sizeof(bios_names.eu)-1] = 0;
886         } else  strcpy(bios_names.eu, "NOT FOUND");
887
888         if (emu_findBios(1, &bios)) { // JP
889                 for (p = bios+strlen(bios)-1; p > bios && *p != '/'; p--); p++;
890                 strncpy(bios_names.jp, p, sizeof(bios_names.jp)); bios_names.jp[sizeof(bios_names.jp)-1] = 0;
891         } else  strcpy(bios_names.jp, "NOT FOUND");
892
893         for(;;)
894         {
895                 draw_cd_menu_options(menu_sel, &bios_names);
896                 inp = wait_for_input(BTN_UP|BTN_DOWN|BTN_LEFT|BTN_RIGHT|BTN_X|BTN_CIRCLE);
897                 if (inp & BTN_UP  ) { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; }
898                 if (inp & BTN_DOWN) { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; }
899                 selected_id = me_index2id(cdopt_entries, CDOPT_ENTRY_COUNT, menu_sel);
900                 if (inp & (BTN_LEFT|BTN_RIGHT)) { // multi choise
901                         if (!me_process(cdopt_entries, CDOPT_ENTRY_COUNT, selected_id, (inp&BTN_RIGHT) ? 1 : 0) &&
902                             selected_id == MA_CDOPT_READAHEAD) {
903                                 if (inp & BTN_LEFT) {
904                                         PicoCDBuffers >>= 1;
905                                         if (PicoCDBuffers < 64) PicoCDBuffers = 0;
906                                 } else {
907                                         if (PicoCDBuffers < 64) PicoCDBuffers = 64;
908                                         else PicoCDBuffers <<= 1;
909                                         if (PicoCDBuffers > 8*1024) PicoCDBuffers = 8*1024; // 16M
910                                 }
911                         }
912                 }
913                 if (inp & BTN_X) { // toggleable options
914                         if (!me_process(cdopt_entries, CDOPT_ENTRY_COUNT, selected_id, 1) &&
915                             selected_id == MA_CDOPT_DONE) {
916                                 return;
917                         }
918                         switch (selected_id) { // BIOS testers
919                                 case MA_CDOPT_TESTBIOS_USA:
920                                         if (emu_findBios(4, &bios)) { // test US
921                                                 strcpy(romFileName, bios);
922                                                 engineState = PGS_ReloadRom;
923                                                 return;
924                                         }
925                                         break;
926                                 case MA_CDOPT_TESTBIOS_EUR:
927                                         if (emu_findBios(8, &bios)) { // test EU
928                                                 strcpy(romFileName, bios);
929                                                 engineState = PGS_ReloadRom;
930                                                 return;
931                                         }
932                                         break;
933                                 case MA_CDOPT_TESTBIOS_JAP:
934                                         if (emu_findBios(1, &bios)) { // test JP
935                                                 strcpy(romFileName, bios);
936                                                 engineState = PGS_ReloadRom;
937                                                 return;
938                                         }
939                                         break;
940                                 default:
941                                         break;
942                         }
943                 }
944                 if (inp & BTN_CIRCLE) return;
945         }
946 }
947
948
949 // --------- advanced options ----------
950
951 menu_entry opt2_entries[] =
952 {
953         { "Emulate Z80",               MB_ONOFF, MA_OPT2_ENABLE_Z80,    &currentConfig.PicoOpt,0x0004, 0, 0, 1 },
954         { "Emulate YM2612 (FM)",       MB_ONOFF, MA_OPT2_ENABLE_YM2612, &currentConfig.PicoOpt,0x0001, 0, 0, 1 },
955         { "Emulate SN76496 (PSG)",     MB_ONOFF, MA_OPT2_ENABLE_SN76496,&currentConfig.PicoOpt,0x0002, 0, 0, 1 },
956 //      { "Double buffering",          MB_ONOFF, MA_OPT2_DBLBUFF,       &currentConfig.EmuOpt, 0x8000, 0, 0, 1 },
957 //      { "Wait for V-sync (slow)",    MB_ONOFF, MA_OPT2_VSYNC,         &currentConfig.EmuOpt, 0x2000, 0, 0, 1 },
958         { "gzip savestates",           MB_ONOFF, MA_OPT2_GZIP_STATES,   &currentConfig.EmuOpt, 0x0008, 0, 0, 1 },
959         { "Don't save last used ROM",  MB_ONOFF, MA_OPT2_NO_LAST_ROM,   &currentConfig.EmuOpt, 0x0020, 0, 0, 1 },
960         { "done",                      MB_NONE,  MA_OPT2_DONE,          NULL, 0, 0, 0, 1 },
961 };
962
963 #define OPT2_ENTRY_COUNT (sizeof(opt2_entries) / sizeof(opt2_entries[0]))
964
965
966 static void draw_amenu_options(int menu_sel)
967 {
968         int tl_x = 80+25, tl_y = 16+50;
969
970         menu_draw_begin();
971
972         menu_draw_selection(tl_x - 16, tl_y + menu_sel*10, 252);
973
974         me_draw(opt2_entries, OPT2_ENTRY_COUNT, tl_x, tl_y, NULL, NULL);
975
976         menu_draw_end();
977 }
978
979 static void amenu_loop_options(void)
980 {
981         static int menu_sel = 0;
982         int menu_sel_max;
983         unsigned long inp = 0;
984         menu_id selected_id;
985
986         menu_sel_max = me_count_enabled(opt2_entries, OPT2_ENTRY_COUNT) - 1;
987
988         for(;;)
989         {
990                 draw_amenu_options(menu_sel);
991                 inp = wait_for_input(BTN_UP|BTN_DOWN|BTN_LEFT|BTN_RIGHT|BTN_X|BTN_CIRCLE);
992                 if (inp & BTN_UP  ) { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; }
993                 if (inp & BTN_DOWN) { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; }
994                 selected_id = me_index2id(opt2_entries, OPT2_ENTRY_COUNT, menu_sel);
995                 if (inp & (BTN_LEFT|BTN_RIGHT)) { // multi choise
996                         if (!me_process(opt2_entries, OPT2_ENTRY_COUNT, selected_id, (inp&BTN_RIGHT) ? 1 : 0) &&
997                             selected_id == MA_OPT2_GAMMA) {
998                                 while ((inp = psp_pad_read(1)) & (BTN_LEFT|BTN_RIGHT)) {
999                                         currentConfig.gamma += (inp & BTN_LEFT) ? -1 : 1;
1000                                         if (currentConfig.gamma <   1) currentConfig.gamma =   1;
1001                                         if (currentConfig.gamma > 300) currentConfig.gamma = 300;
1002                                         draw_amenu_options(menu_sel);
1003                                         psp_msleep(18);
1004                                 }
1005                         }
1006                 }
1007                 if (inp & BTN_X) { // toggleable options
1008                         if (!me_process(opt2_entries, OPT2_ENTRY_COUNT, selected_id, 1) &&
1009                             selected_id == MA_OPT2_DONE) {
1010                                 return;
1011                         }
1012                 }
1013                 if (inp & BTN_CIRCLE) return;
1014         }
1015 }
1016
1017 // -------------- options --------------
1018
1019
1020 menu_entry opt_entries[] =
1021 {
1022         { NULL,                        MB_NONE,  MA_OPT_RENDERER,      NULL, 0, 0, 0, 1 },
1023         { "Scale low res mode",        MB_ONOFF, MA_OPT_SCALING,       &currentConfig.scaling, 0x0001, 0, 3, 1 },
1024         { "Accurate timing (slower)",  MB_ONOFF, MA_OPT_ACC_TIMING,    &currentConfig.PicoOpt, 0x0040, 0, 0, 1 },
1025         { "Accurate sprites (slower)", MB_ONOFF, MA_OPT_ACC_SPRITES,   &currentConfig.PicoOpt, 0x0080, 0, 0, 1 },
1026         { "Show FPS",                  MB_ONOFF, MA_OPT_SHOW_FPS,      &currentConfig.EmuOpt,  0x0002, 0, 0, 1 },
1027         { NULL,                        MB_RANGE, MA_OPT_FRAMESKIP,     &currentConfig.Frameskip, 0, -1, 16, 1 },
1028         { "Enable sound",              MB_ONOFF, MA_OPT_ENABLE_SOUND,  &currentConfig.EmuOpt,  0x0004, 0, 0, 1 },
1029         { NULL,                        MB_NONE,  MA_OPT_SOUND_QUALITY, NULL, 0, 0, 0, 1 },
1030         { "6 button pad",              MB_ONOFF, MA_OPT_6BUTTON_PAD,   &currentConfig.PicoOpt, 0x0020, 0, 0, 1 },
1031         { NULL,                        MB_NONE,  MA_OPT_REGION,        NULL, 0, 0, 0, 1 },
1032         { "Use SRAM/BRAM savestates",  MB_ONOFF, MA_OPT_SRAM_STATES,   &currentConfig.EmuOpt,  0x0001, 0, 0, 1 },
1033         { NULL,                        MB_NONE,  MA_OPT_CONFIRM_STATES,NULL, 0, 0, 0, 1 },
1034         { "Save slot",                 MB_RANGE, MA_OPT_SAVE_SLOT,     &state_slot, 0, 0, 9, 1 },
1035         { "[Sega/Mega CD options]",    MB_NONE,  MA_OPT_SCD_OPTS,      NULL, 0, 0, 0, 1 },
1036         { "[advanced options]",        MB_NONE,  MA_OPT_ADV_OPTS,      NULL, 0, 0, 0, 1 },
1037         { NULL,                        MB_NONE,  MA_OPT_SAVECFG,       NULL, 0, 0, 0, 1 },
1038         { "Save cfg for current game only",MB_NONE,MA_OPT_SAVECFG_GAME,NULL, 0, 0, 0, 1 },
1039         { NULL,                        MB_NONE,  MA_OPT_LOADCFG,       NULL, 0, 0, 0, 1 },
1040 };
1041
1042 #define OPT_ENTRY_COUNT (sizeof(opt_entries) / sizeof(opt_entries[0]))
1043
1044
1045 static const char *region_name(unsigned int code)
1046 {
1047         static const char *names[] = { "Auto", "      Japan NTSC", "      Japan PAL", "      USA", "      Europe" };
1048         static const char *names_short[] = { "", " JP", " JP", " US", " EU" };
1049         int u, i = 0;
1050         if (code) {
1051                 code <<= 1;
1052                 while((code >>= 1)) i++;
1053                 if (i > 4) return "unknown";
1054                 return names[i];
1055         } else {
1056                 static char name[24];
1057                 strcpy(name, "Auto:");
1058                 for (u = 0; u < 3; u++) {
1059                         i = 0; code = ((PicoAutoRgnOrder >> u*4) & 0xf) << 1;
1060                         while((code >>= 1)) i++;
1061                         strcat(name, names_short[i]);
1062                 }
1063                 return name;
1064         }
1065 }
1066
1067
1068 static void menu_opt_cust_draw(const menu_entry *entry, int x, int y, void *param)
1069 {
1070         char *str, str24[24];
1071
1072         switch (entry->id)
1073         {
1074                 case MA_OPT_RENDERER:
1075                         if (currentConfig.PicoOpt&0x10)
1076                                 str = " 8bit fast";
1077                         else if (currentConfig.EmuOpt&0x80)
1078                                 str = "16bit accurate";
1079                         else
1080                                 str = " 8bit accurate";
1081                         text_out16(x, y, "Renderer:            %s", str);
1082                         break;
1083                 case MA_OPT_FRAMESKIP:
1084                         if (currentConfig.Frameskip < 0)
1085                              strcpy(str24, "Auto");
1086                         else sprintf(str24, "%i", currentConfig.Frameskip);
1087                         text_out16(x, y, "Frameskip                  %s", str24);
1088                         break;
1089                 case MA_OPT_SOUND_QUALITY:
1090                         str = (currentConfig.PicoOpt&0x08)?"stereo":"mono";
1091                         text_out16(x, y, "Sound Quality:     %5iHz %s", currentConfig.PsndRate, str);
1092                         break;
1093                 case MA_OPT_REGION:
1094                         text_out16(x, y, "Region:              %s", region_name(currentConfig.PicoRegion));
1095                         break;
1096                 case MA_OPT_CONFIRM_STATES:
1097                         switch ((currentConfig.EmuOpt >> 9) & 5) {
1098                                 default: str = "OFF";    break;
1099                                 case 1:  str = "writes"; break;
1100                                 case 4:  str = "loads";  break;
1101                                 case 5:  str = "both";   break;
1102                         }
1103                         text_out16(x, y, "Confirm savestate          %s", str);
1104                         break;
1105                 case MA_OPT_SAVECFG:
1106                         str24[0] = 0;
1107                         if (config_slot != 0) sprintf(str24, " (profile: %i)", config_slot);
1108                         text_out16(x, y, "Save cfg as default%s", str24);
1109                         break;
1110                 case MA_OPT_LOADCFG:
1111                         text_out16(x, y, "Load cfg from profile %i", config_slot);
1112                         break;
1113                 default:
1114                         lprintf("%s: unimplemented (%i)\n", __FUNCTION__, entry->id);
1115                         break;
1116         }
1117 }
1118
1119
1120
1121 static void draw_menu_options(int menu_sel)
1122 {
1123         int tl_x = 80+25, tl_y = 16+24;
1124
1125         menu_draw_begin();
1126
1127         menu_draw_selection(tl_x - 16, tl_y + menu_sel*10, 284);
1128
1129         me_draw(opt_entries, OPT_ENTRY_COUNT, tl_x, tl_y, menu_opt_cust_draw, NULL);
1130
1131         menu_draw_end();
1132 }
1133
1134 static int sndrate_prevnext(int rate, int dir)
1135 {
1136         int i, rates[] = { 11025, 22050, 44100 };
1137
1138         for (i = 0; i < 5; i++)
1139                 if (rates[i] == rate) break;
1140
1141         i += dir ? 1 : -1;
1142         if (i > 2) return dir ? 44100 : 22050;
1143         if (i < 0) return dir ? 22050 : 11025;
1144         return rates[i];
1145 }
1146
1147 static void region_prevnext(int right)
1148 {
1149         // jp_ntsc=1, jp_pal=2, usa=4, eu=8
1150         static int rgn_orders[] = { 0x148, 0x184, 0x814, 0x418, 0x841, 0x481 };
1151         int i;
1152         if (right) {
1153                 if (!currentConfig.PicoRegion) {
1154                         for (i = 0; i < 6; i++)
1155                                 if (rgn_orders[i] == PicoAutoRgnOrder) break;
1156                         if (i < 5) PicoAutoRgnOrder = rgn_orders[i+1];
1157                         else currentConfig.PicoRegion=1;
1158                 }
1159                 else currentConfig.PicoRegion<<=1;
1160                 if (currentConfig.PicoRegion > 8) currentConfig.PicoRegion = 8;
1161         } else {
1162                 if (!currentConfig.PicoRegion) {
1163                         for (i = 0; i < 6; i++)
1164                                 if (rgn_orders[i] == PicoAutoRgnOrder) break;
1165                         if (i > 0) PicoAutoRgnOrder = rgn_orders[i-1];
1166                 }
1167                 else currentConfig.PicoRegion>>=1;
1168         }
1169 }
1170
1171 static void menu_options_save(void)
1172 {
1173         PicoOpt = currentConfig.PicoOpt;
1174         PsndRate = currentConfig.PsndRate;
1175         PicoRegionOverride = currentConfig.PicoRegion;
1176         if (!(PicoOpt & 0x20)) {
1177                 // unbind XYZ MODE, just in case
1178                 unbind_action(0xf00);
1179         }
1180 }
1181
1182 static int menu_loop_options(void)
1183 {
1184         static int menu_sel = 0;
1185         int menu_sel_max, ret;
1186         unsigned long inp = 0;
1187         menu_id selected_id;
1188
1189         currentConfig.PicoOpt = PicoOpt;
1190         currentConfig.PsndRate = PsndRate;
1191         currentConfig.PicoRegion = PicoRegionOverride;
1192
1193         me_enable(opt_entries, OPT_ENTRY_COUNT, MA_OPT_SAVECFG_GAME, rom_data != NULL);
1194         me_enable(opt_entries, OPT_ENTRY_COUNT, MA_OPT_LOADCFG, config_slot != config_slot_current);
1195         menu_sel_max = me_count_enabled(opt_entries, OPT_ENTRY_COUNT) - 1;
1196         if (menu_sel > menu_sel_max) menu_sel = menu_sel_max;
1197
1198         while (1)
1199         {
1200                 draw_menu_options(menu_sel);
1201                 inp = wait_for_input(BTN_UP|BTN_DOWN|BTN_LEFT|BTN_RIGHT|BTN_X|BTN_CIRCLE);
1202                 if (inp & BTN_UP  ) { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; }
1203                 if (inp & BTN_DOWN) { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; }
1204                 selected_id = me_index2id(opt_entries, OPT_ENTRY_COUNT, menu_sel);
1205                 if (inp & (BTN_LEFT|BTN_RIGHT)) { // multi choise
1206                         if (!me_process(opt_entries, OPT_ENTRY_COUNT, selected_id, (inp&BTN_RIGHT) ? 1 : 0)) {
1207                                 switch (selected_id) {
1208                                         case MA_OPT_RENDERER:
1209                                                 if (inp & BTN_LEFT) {
1210                                                         if ((currentConfig.PicoOpt&0x10) || !(currentConfig.EmuOpt &0x80)) {
1211                                                                 currentConfig.PicoOpt&= ~0x10;
1212                                                                 currentConfig.EmuOpt |=  0x80;
1213                                                         }
1214                                                 } else {
1215                                                         if (!(currentConfig.PicoOpt&0x10) || (currentConfig.EmuOpt &0x80)) {
1216                                                                 currentConfig.PicoOpt|=  0x10;
1217                                                                 currentConfig.EmuOpt &= ~0x80;
1218                                                         }
1219                                                 }
1220                                                 break;
1221                                         case MA_OPT_SOUND_QUALITY:
1222                                                 if ((inp & BTN_RIGHT) && currentConfig.PsndRate == 44100 &&
1223                                                                 !(currentConfig.PicoOpt&0x08))
1224                                                 {
1225                                                         currentConfig.PsndRate =  11025;
1226                                                         currentConfig.PicoOpt |=  8;
1227                                                 } else if ((inp & BTN_LEFT) && currentConfig.PsndRate == 11025 &&
1228                                                                 (currentConfig.PicoOpt&0x08) && !(PicoMCD&1))
1229                                                 {
1230                                                         currentConfig.PsndRate =  44100;
1231                                                         currentConfig.PicoOpt &= ~8;
1232                                                 } else
1233                                                         currentConfig.PsndRate = sndrate_prevnext(currentConfig.PsndRate, inp & BTN_RIGHT);
1234                                                 break;
1235                                         case MA_OPT_REGION:
1236                                                 region_prevnext(inp & BTN_RIGHT);
1237                                                 break;
1238                                         case MA_OPT_CONFIRM_STATES: {
1239                                                          int n = ((currentConfig.EmuOpt>>9)&1) | ((currentConfig.EmuOpt>>10)&2);
1240                                                          n += (inp & BTN_LEFT) ? -1 : 1;
1241                                                          if (n < 0) n = 0; else if (n > 3) n = 3;
1242                                                          n |= n << 1; n &= ~2;
1243                                                          currentConfig.EmuOpt &= ~0xa00;
1244                                                          currentConfig.EmuOpt |= n << 9;
1245                                                          break;
1246                                                  }
1247                                         case MA_OPT_SAVE_SLOT:
1248                                                  if (inp & BTN_RIGHT) {
1249                                                          state_slot++; if (state_slot > 9) state_slot = 0;
1250                                                  } else {state_slot--; if (state_slot < 0) state_slot = 9;
1251                                                  }
1252                                                  break;
1253                                         case MA_OPT_SAVECFG:
1254                                         case MA_OPT_SAVECFG_GAME:
1255                                         case MA_OPT_LOADCFG:
1256                                                  config_slot += (inp&BTN_RIGHT) ? 1 : -1;
1257                                                  if (config_slot > 9) config_slot = 0;
1258                                                  if (config_slot < 0) config_slot = 9;
1259                                                  me_enable(opt_entries, OPT_ENTRY_COUNT, MA_OPT_LOADCFG, config_slot != config_slot_current);
1260                                                  menu_sel_max = me_count_enabled(opt_entries, OPT_ENTRY_COUNT) - 1;
1261                                                  if (menu_sel > menu_sel_max) menu_sel = menu_sel_max;
1262                                                  break;
1263                                         default:
1264                                                 //lprintf("%s: something unknown selected (%i)\n", __FUNCTION__, selected_id);
1265                                                 break;
1266                                 }
1267                         }
1268                 }
1269                 if (inp & BTN_X) {
1270                         if (!me_process(opt_entries, OPT_ENTRY_COUNT, selected_id, 1))
1271                         {
1272                                 switch (selected_id)
1273                                 {
1274                                         case MA_OPT_SCD_OPTS:
1275                                                 cd_menu_loop_options();
1276                                                 if (engineState == PGS_ReloadRom)
1277                                                         return 0; // test BIOS
1278                                                 break;
1279                                         case MA_OPT_ADV_OPTS:
1280                                                 amenu_loop_options();
1281                                                 break;
1282                                         case MA_OPT_SAVECFG: // done (update and write)
1283                                                 menu_options_save();
1284                                                 if (emu_WriteConfig(0)) strcpy(menuErrorMsg, "config saved");
1285                                                 else strcpy(menuErrorMsg, "failed to write config");
1286                                                 return 1;
1287                                         case MA_OPT_SAVECFG_GAME: // done (update and write for current game)
1288                                                 menu_options_save();
1289                                                 if (emu_WriteConfig(1)) strcpy(menuErrorMsg, "config saved");
1290                                                 else strcpy(menuErrorMsg, "failed to write config");
1291                                                 return 1;
1292                                         case MA_OPT_LOADCFG:
1293                                                 ret = emu_ReadConfig(1, 1);
1294                                                 if (!ret) ret = emu_ReadConfig(0, 1);
1295                                                 if (ret)  strcpy(menuErrorMsg, "config loaded");
1296                                                 else      strcpy(menuErrorMsg, "failed to load config");
1297                                                 return 1;
1298                                         default:
1299                                                 //lprintf("%s: something unknown selected (%i)\n", __FUNCTION__, selected_id);
1300                                                 break;
1301                                 }
1302                         }
1303                 }
1304                 if(inp & BTN_CIRCLE) {
1305                         menu_options_save();
1306                         return 0;  // done (update, no write)
1307                 }
1308         }
1309 }
1310
1311 // -------------- credits --------------
1312
1313 static void draw_menu_credits(void)
1314 {
1315         int tl_x = 80+15, tl_y = 16+64, y;
1316         menu_draw_begin();
1317
1318         text_out16(tl_x, 16+20, "PicoDrive v" VERSION " (c) notaz, 2006,2007");
1319
1320         y = tl_y;
1321         text_out16(tl_x, y, "Credits:");
1322         text_out16(tl_x, (y+=10), "Dave: base code of PicoDrive");
1323         text_out16(tl_x, (y+=10), "MAME devs: YM2612 and SN76496 cores");
1324         text_out16(tl_x, (y+=10), "Charles MacDonald: Genesis hw docs");
1325         text_out16(tl_x, (y+=10), "Stephane Dallongeville:");
1326         text_out16(tl_x, (y+=10), "      opensource Gens");
1327         text_out16(tl_x, (y+=10), "Haze: Genesis hw info");
1328         text_out16(tl_x, (y+=10), "ketchupgun: skin design");
1329
1330         menu_draw_end();
1331 }
1332
1333
1334 // -------------- root menu --------------
1335
1336 menu_entry main_entries[] =
1337 {
1338         { "Resume game",        MB_NONE, MA_MAIN_RESUME_GAME, NULL, 0, 0, 0, 0 },
1339         { "Save State",         MB_NONE, MA_MAIN_SAVE_STATE,  NULL, 0, 0, 0, 0 },
1340         { "Load State",         MB_NONE, MA_MAIN_LOAD_STATE,  NULL, 0, 0, 0, 0 },
1341         { "Reset game",         MB_NONE, MA_MAIN_RESET_GAME,  NULL, 0, 0, 0, 0 },
1342         { "Load new ROM/ISO",   MB_NONE, MA_MAIN_LOAD_ROM,    NULL, 0, 0, 0, 1 },
1343         { "Change options",     MB_NONE, MA_MAIN_OPTIONS,     NULL, 0, 0, 0, 1 },
1344         { "Configure controls", MB_NONE, MA_MAIN_CONTROLS,    NULL, 0, 0, 0, 1 },
1345         { "Credits",            MB_NONE, MA_MAIN_CREDITS,     NULL, 0, 0, 0, 1 },
1346         { "Patches / GameGenie",MB_NONE, MA_MAIN_PATCHES,     NULL, 0, 0, 0, 0 },
1347         { "Exit",               MB_NONE, MA_MAIN_EXIT,        NULL, 0, 0, 0, 1 }
1348 };
1349
1350 #define MAIN_ENTRY_COUNT (sizeof(main_entries) / sizeof(main_entries[0]))
1351
1352 static void draw_menu_root(int menu_sel)
1353 {
1354         const int tl_x = 80+70, tl_y = 16+70;
1355
1356         menu_draw_begin();
1357
1358         text_out16(tl_x, 16+20, "PicoDrive v" VERSION);
1359
1360         menu_draw_selection(tl_x - 16, tl_y + menu_sel*10, 146);
1361
1362         me_draw(main_entries, MAIN_ENTRY_COUNT, tl_x, tl_y, NULL, NULL);
1363
1364         // error
1365         if (menuErrorMsg[0]) {
1366                 // memset((char *)menu_screen + 321*224*2, 0, 321*16*2);
1367                 text_out16(5, 258, menuErrorMsg);
1368         }
1369         menu_draw_end();
1370 }
1371
1372
1373 static void menu_loop_root(void)
1374 {
1375         static int menu_sel = 0;
1376         int ret, menu_sel_max;
1377         unsigned long inp = 0;
1378
1379         me_enable(main_entries, MAIN_ENTRY_COUNT, MA_MAIN_RESUME_GAME, rom_data != NULL);
1380         me_enable(main_entries, MAIN_ENTRY_COUNT, MA_MAIN_SAVE_STATE,  rom_data != NULL);
1381         me_enable(main_entries, MAIN_ENTRY_COUNT, MA_MAIN_LOAD_STATE,  rom_data != NULL);
1382         me_enable(main_entries, MAIN_ENTRY_COUNT, MA_MAIN_RESET_GAME,  rom_data != NULL);
1383         me_enable(main_entries, MAIN_ENTRY_COUNT, MA_MAIN_PATCHES,     PicoPatches != NULL);
1384
1385         menu_sel_max = me_count_enabled(main_entries, MAIN_ENTRY_COUNT) - 1;
1386         if (menu_sel > menu_sel_max) menu_sel = menu_sel_max;
1387
1388         /* make sure action buttons are not pressed on entering menu */
1389         draw_menu_root(menu_sel);
1390
1391         while (psp_pad_read(1) & (BTN_X|BTN_CIRCLE|BTN_SELECT)) psp_msleep(50);
1392
1393         for (;;)
1394         {
1395                 draw_menu_root(menu_sel);
1396                 inp = wait_for_input(BTN_UP|BTN_DOWN|BTN_X|BTN_CIRCLE|BTN_SELECT|BTN_L|BTN_R);
1397                 if(inp & BTN_UP  )  { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; }
1398                 if(inp & BTN_DOWN)  { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; }
1399                 if((inp & (BTN_L|BTN_R)) == (BTN_L|BTN_R)) debug_menu_loop();
1400                 if( inp & (BTN_SELECT|BTN_CIRCLE)) {
1401                         if (rom_data) {
1402                                 while (psp_pad_read(1) & (BTN_SELECT|BTN_CIRCLE)) psp_msleep(50); // wait until released
1403                                 engineState = PGS_Running;
1404                                 break;
1405                         }
1406                 }
1407                 if(inp & BTN_X)  {
1408                         switch (me_index2id(main_entries, MAIN_ENTRY_COUNT, menu_sel))
1409                         {
1410                                 case MA_MAIN_RESUME_GAME:
1411                                         if (rom_data) {
1412                                                 while (psp_pad_read(1) & BTN_X) psp_msleep(50);
1413                                                 engineState = PGS_Running;
1414                                                 return;
1415                                         }
1416                                         break;
1417                                 case MA_MAIN_SAVE_STATE:
1418                                         if (rom_data) {
1419                                                 if(savestate_menu_loop(0))
1420                                                         continue;
1421                                                 engineState = PGS_Running;
1422                                                 return;
1423                                         }
1424                                         break;
1425                                 case MA_MAIN_LOAD_STATE:
1426                                         if (rom_data) {
1427                                                 if(savestate_menu_loop(1))
1428                                                         continue;
1429                                                 engineState = PGS_Running;
1430                                                 return;
1431                                         }
1432                                         break;
1433                                 case MA_MAIN_RESET_GAME:
1434                                         if (rom_data) {
1435                                                 emu_ResetGame();
1436                                                 engineState = PGS_Running;
1437                                                 return;
1438                                         }
1439                                         break;
1440                                 case MA_MAIN_LOAD_ROM:
1441                                 {
1442                                         char curr_path[PATH_MAX], *selfname;
1443                                         FILE *tstf;
1444                                         if ( (tstf = fopen(currentConfig.lastRomFile, "rb")) )
1445                                         {
1446                                                 fclose(tstf);
1447                                                 strcpy(curr_path, currentConfig.lastRomFile);
1448                                         }
1449                                         else
1450                                                 getcwd(curr_path, PATH_MAX);
1451                                         selfname = romsel_loop(curr_path);
1452                                         if (selfname) {
1453                                                 lprintf("selected file: %s\n", selfname);
1454                                                 engineState = PGS_ReloadRom;
1455                                                 return;
1456                                         }
1457                                         break;
1458                                 }
1459                                 case MA_MAIN_OPTIONS:
1460                                         ret = menu_loop_options();
1461                                         if (ret == 1) continue; // status update
1462                                         if (engineState == PGS_ReloadRom)
1463                                                 return; // BIOS test
1464                                         break;
1465                                 case MA_MAIN_CONTROLS:
1466                                         kc_sel_loop();
1467                                         break;
1468                                 case MA_MAIN_CREDITS:
1469                                         draw_menu_credits();
1470                                         psp_msleep(500);
1471                                         inp = wait_for_input(BTN_X|BTN_CIRCLE);
1472                                         break;
1473                                 case MA_MAIN_EXIT:
1474                                         engineState = PGS_Quit;
1475                                         return;
1476                                 case MA_MAIN_PATCHES:
1477                                         if (rom_data && PicoPatches) {
1478                                                 patches_menu_loop();
1479                                                 PicoPatchApply();
1480                                                 strcpy(menuErrorMsg, "Patches applied");
1481                                                 continue;
1482                                         }
1483                                         break;
1484                                 default:
1485                                         lprintf("%s: something unknown selected\n", __FUNCTION__);
1486                                         break;
1487                         }
1488                 }
1489                 menuErrorMsg[0] = 0; // clear error msg
1490         }
1491 }
1492
1493 // warning: alignment
1494 static void menu_darken_bg(void *dst, const void *src, int pixels, int darker)
1495 {
1496         unsigned int *dest = dst;
1497         const unsigned int *srce = src;
1498         pixels /= 2;
1499         if (darker)
1500         {
1501                 while (pixels--)
1502                 {
1503                         unsigned int p = *srce++;
1504                         *dest++ = ((p&0xf79ef79e)>>1) - ((p&0xc618c618)>>3);
1505                 }
1506         }
1507         else
1508         {
1509                 while (pixels--)
1510                 {
1511                         unsigned int p = *srce++;
1512                         *dest++ = (p&0xf79ef79e)>>1;
1513                 }
1514         }
1515 }
1516
1517 static void menu_prepare_bg(int use_game_bg)
1518 {
1519         memset(bg_buffer, 0, sizeof(bg_buffer));
1520
1521         if (use_game_bg)
1522         {
1523                 // darken the active framebuffer
1524                 /*
1525                 memset(bg_buffer, 0, 321*8*2);
1526                 menu_darken_bg(bg_buffer + 321*8*2, (char *)giz_screen + 321*8*2, 321*224, 1);
1527                 memset(bg_buffer + 321*232*2, 0, 321*8*2);
1528                 */
1529         }
1530         else
1531         {
1532                 // should really only happen once, on startup..
1533                 readpng(bg_buffer, "skin/background.png", READPNG_BG);
1534         }
1535 }
1536
1537 static void menu_gfx_prepare(void)
1538 {
1539         menu_prepare_bg(rom_data != NULL);
1540
1541         menu_draw_begin();
1542         menu_draw_end();
1543 }
1544
1545
1546 void menu_loop(void)
1547 {
1548         menu_gfx_prepare();
1549
1550         menu_loop_root();
1551
1552         menuErrorMsg[0] = 0;
1553 }
1554
1555
1556 // --------- CD tray close menu ----------
1557
1558 static void draw_menu_tray(int menu_sel)
1559 {
1560         int tl_x = 70, tl_y = 90, y;
1561
1562         menu_draw_begin();
1563
1564         text_out16(tl_x, 20, "The unit is about to");
1565         text_out16(tl_x, 30, "close the CD tray.");
1566
1567         y = tl_y;
1568         text_out16(tl_x, y,       "Load new CD image");
1569         text_out16(tl_x, (y+=10), "Insert nothing");
1570
1571         // draw cursor
1572         text_out16(tl_x - 16, tl_y + menu_sel*10, ">");
1573         // error
1574         if (menuErrorMsg[0]) text_out16(5, 226, menuErrorMsg);
1575         menu_draw_end();
1576 }
1577
1578
1579 int menu_loop_tray(void)
1580 {
1581         int menu_sel = 0, menu_sel_max = 1;
1582         unsigned long inp = 0;
1583         char curr_path[PATH_MAX], *selfname;
1584         FILE *tstf;
1585
1586         menu_gfx_prepare();
1587
1588         if ( (tstf = fopen(currentConfig.lastRomFile, "rb")) )
1589         {
1590                 fclose(tstf);
1591                 strcpy(curr_path, currentConfig.lastRomFile);
1592         }
1593         else
1594         {
1595                 getcwd(curr_path, PATH_MAX);
1596         }
1597
1598         /* make sure action buttons are not pressed on entering menu */
1599         draw_menu_tray(menu_sel);
1600         while (psp_pad_read(1) & BTN_X) psp_msleep(50);
1601
1602         for (;;)
1603         {
1604                 draw_menu_tray(menu_sel);
1605                 inp = wait_for_input(BTN_UP|BTN_DOWN|BTN_X);
1606                 if(inp & BTN_UP  )  { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; }
1607                 if(inp & BTN_DOWN)  { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; }
1608                 if(inp & BTN_X   )  {
1609                         switch (menu_sel) {
1610                                 case 0: // select image
1611                                         selfname = romsel_loop(curr_path);
1612                                         if (selfname) {
1613                                                 int ret = -1, cd_type;
1614                                                 cd_type = emu_cdCheck(NULL);
1615                                                 if (cd_type > 0)
1616                                                         ret = Insert_CD(romFileName, cd_type == 2);
1617                                                 if (ret != 0) {
1618                                                         sprintf(menuErrorMsg, "Load failed, invalid CD image?");
1619                                                         lprintf("%s\n", menuErrorMsg);
1620                                                         continue;
1621                                                 }
1622                                                 engineState = PGS_RestartRun;
1623                                                 return 1;
1624                                         }
1625                                         break;
1626                                 case 1: // insert nothing
1627                                         engineState = PGS_RestartRun;
1628                                         return 0;
1629                         }
1630                 }
1631                 menuErrorMsg[0] = 0; // clear error msg
1632         }
1633 }
1634
1635