FAME + some random stuff added
[picodrive.git] / platform / 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         { NULL,                        MB_NONE,  MA_OPT_CPU_CLOCKS,    NULL, 0, 0, 0, 1 },
1036         { "[Sega/Mega CD options]",    MB_NONE,  MA_OPT_SCD_OPTS,      NULL, 0, 0, 0, 1 },
1037         { "[advanced options]",        MB_NONE,  MA_OPT_ADV_OPTS,      NULL, 0, 0, 0, 1 },
1038         { NULL,                        MB_NONE,  MA_OPT_SAVECFG,       NULL, 0, 0, 0, 1 },
1039         { "Save cfg for current game only",MB_NONE,MA_OPT_SAVECFG_GAME,NULL, 0, 0, 0, 1 },
1040         { NULL,                        MB_NONE,  MA_OPT_LOADCFG,       NULL, 0, 0, 0, 1 },
1041 };
1042
1043 #define OPT_ENTRY_COUNT (sizeof(opt_entries) / sizeof(opt_entries[0]))
1044
1045
1046 static const char *region_name(unsigned int code)
1047 {
1048         static const char *names[] = { "Auto", "      Japan NTSC", "      Japan PAL", "      USA", "      Europe" };
1049         static const char *names_short[] = { "", " JP", " JP", " US", " EU" };
1050         int u, i = 0;
1051         if (code) {
1052                 code <<= 1;
1053                 while((code >>= 1)) i++;
1054                 if (i > 4) return "unknown";
1055                 return names[i];
1056         } else {
1057                 static char name[24];
1058                 strcpy(name, "Auto:");
1059                 for (u = 0; u < 3; u++) {
1060                         i = 0; code = ((PicoAutoRgnOrder >> u*4) & 0xf) << 1;
1061                         while((code >>= 1)) i++;
1062                         strcat(name, names_short[i]);
1063                 }
1064                 return name;
1065         }
1066 }
1067
1068
1069 static void menu_opt_cust_draw(const menu_entry *entry, int x, int y, void *param)
1070 {
1071         char *str, str24[24];
1072
1073         switch (entry->id)
1074         {
1075                 case MA_OPT_RENDERER:
1076                         if (currentConfig.PicoOpt&0x10)
1077                                 str = " 8bit fast";
1078                         else if (currentConfig.EmuOpt&0x80)
1079                                 str = "16bit accurate";
1080                         else
1081                                 str = " 8bit accurate";
1082                         text_out16(x, y, "Renderer:            %s", str);
1083                         break;
1084                 case MA_OPT_FRAMESKIP:
1085                         if (currentConfig.Frameskip < 0)
1086                              strcpy(str24, "Auto");
1087                         else sprintf(str24, "%i", currentConfig.Frameskip);
1088                         text_out16(x, y, "Frameskip                  %s", str24);
1089                         break;
1090                 case MA_OPT_SOUND_QUALITY:
1091                         str = (currentConfig.PicoOpt&0x08)?"stereo":"mono";
1092                         text_out16(x, y, "Sound Quality:     %5iHz %s", currentConfig.PsndRate, str);
1093                         break;
1094                 case MA_OPT_REGION:
1095                         text_out16(x, y, "Region:              %s", region_name(currentConfig.PicoRegion));
1096                         break;
1097                 case MA_OPT_CONFIRM_STATES:
1098                         switch ((currentConfig.EmuOpt >> 9) & 5) {
1099                                 default: str = "OFF";    break;
1100                                 case 1:  str = "writes"; break;
1101                                 case 4:  str = "loads";  break;
1102                                 case 5:  str = "both";   break;
1103                         }
1104                         text_out16(x, y, "Confirm savestate          %s", str);
1105                         break;
1106                 case MA_OPT_CPU_CLOCKS:
1107                         text_out16(x, y, "CPU/bus clock       %3i/%3iMHz", currentConfig.CPUclock, currentConfig.CPUclock/2);
1108                         break;
1109                 case MA_OPT_SAVECFG:
1110                         str24[0] = 0;
1111                         if (config_slot != 0) sprintf(str24, " (profile: %i)", config_slot);
1112                         text_out16(x, y, "Save cfg as default%s", str24);
1113                         break;
1114                 case MA_OPT_LOADCFG:
1115                         text_out16(x, y, "Load cfg from profile %i", config_slot);
1116                         break;
1117                 default:
1118                         lprintf("%s: unimplemented (%i)\n", __FUNCTION__, entry->id);
1119                         break;
1120         }
1121 }
1122
1123
1124
1125 static void draw_menu_options(int menu_sel)
1126 {
1127         int tl_x = 80+25, tl_y = 16+24;
1128
1129         menu_draw_begin();
1130
1131         menu_draw_selection(tl_x - 16, tl_y + menu_sel*10, 284);
1132
1133         me_draw(opt_entries, OPT_ENTRY_COUNT, tl_x, tl_y, menu_opt_cust_draw, NULL);
1134
1135         menu_draw_end();
1136 }
1137
1138 static int sndrate_prevnext(int rate, int dir)
1139 {
1140         int i, rates[] = { 11025, 22050, 44100 };
1141
1142         for (i = 0; i < 5; i++)
1143                 if (rates[i] == rate) break;
1144
1145         i += dir ? 1 : -1;
1146         if (i > 2) return dir ? 44100 : 22050;
1147         if (i < 0) return dir ? 22050 : 11025;
1148         return rates[i];
1149 }
1150
1151 static void region_prevnext(int right)
1152 {
1153         // jp_ntsc=1, jp_pal=2, usa=4, eu=8
1154         static int rgn_orders[] = { 0x148, 0x184, 0x814, 0x418, 0x841, 0x481 };
1155         int i;
1156         if (right) {
1157                 if (!currentConfig.PicoRegion) {
1158                         for (i = 0; i < 6; i++)
1159                                 if (rgn_orders[i] == PicoAutoRgnOrder) break;
1160                         if (i < 5) PicoAutoRgnOrder = rgn_orders[i+1];
1161                         else currentConfig.PicoRegion=1;
1162                 }
1163                 else currentConfig.PicoRegion<<=1;
1164                 if (currentConfig.PicoRegion > 8) currentConfig.PicoRegion = 8;
1165         } else {
1166                 if (!currentConfig.PicoRegion) {
1167                         for (i = 0; i < 6; i++)
1168                                 if (rgn_orders[i] == PicoAutoRgnOrder) break;
1169                         if (i > 0) PicoAutoRgnOrder = rgn_orders[i-1];
1170                 }
1171                 else currentConfig.PicoRegion>>=1;
1172         }
1173 }
1174
1175 static void menu_options_save(void)
1176 {
1177         PicoOpt = currentConfig.PicoOpt;
1178         PsndRate = currentConfig.PsndRate;
1179         PicoRegionOverride = currentConfig.PicoRegion;
1180         if (!(PicoOpt & 0x20)) {
1181                 // unbind XYZ MODE, just in case
1182                 unbind_action(0xf00);
1183         }
1184 }
1185
1186 static int menu_loop_options(void)
1187 {
1188         static int menu_sel = 0;
1189         int menu_sel_max, ret;
1190         unsigned long inp = 0;
1191         menu_id selected_id;
1192
1193         currentConfig.PicoOpt = PicoOpt;
1194         currentConfig.PsndRate = PsndRate;
1195         currentConfig.PicoRegion = PicoRegionOverride;
1196
1197         me_enable(opt_entries, OPT_ENTRY_COUNT, MA_OPT_SAVECFG_GAME, rom_data != NULL);
1198         me_enable(opt_entries, OPT_ENTRY_COUNT, MA_OPT_LOADCFG, config_slot != config_slot_current);
1199         menu_sel_max = me_count_enabled(opt_entries, OPT_ENTRY_COUNT) - 1;
1200         if (menu_sel > menu_sel_max) menu_sel = menu_sel_max;
1201
1202         while (1)
1203         {
1204                 draw_menu_options(menu_sel);
1205                 inp = wait_for_input(BTN_UP|BTN_DOWN|BTN_LEFT|BTN_RIGHT|BTN_X|BTN_CIRCLE);
1206                 if (inp & BTN_UP  ) { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; }
1207                 if (inp & BTN_DOWN) { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; }
1208                 selected_id = me_index2id(opt_entries, OPT_ENTRY_COUNT, menu_sel);
1209                 if (inp & (BTN_LEFT|BTN_RIGHT)) { // multi choise
1210                         if (!me_process(opt_entries, OPT_ENTRY_COUNT, selected_id, (inp&BTN_RIGHT) ? 1 : 0)) {
1211                                 switch (selected_id) {
1212                                         case MA_OPT_RENDERER:
1213                                                 if (inp & BTN_LEFT) {
1214                                                         if ((currentConfig.PicoOpt&0x10) || !(currentConfig.EmuOpt &0x80)) {
1215                                                                 currentConfig.PicoOpt&= ~0x10;
1216                                                                 currentConfig.EmuOpt |=  0x80;
1217                                                         }
1218                                                 } else {
1219                                                         if (!(currentConfig.PicoOpt&0x10) || (currentConfig.EmuOpt &0x80)) {
1220                                                                 currentConfig.PicoOpt|=  0x10;
1221                                                                 currentConfig.EmuOpt &= ~0x80;
1222                                                         }
1223                                                 }
1224                                                 break;
1225                                         case MA_OPT_SOUND_QUALITY:
1226                                                 if ((inp & BTN_RIGHT) && currentConfig.PsndRate == 44100 &&
1227                                                                 !(currentConfig.PicoOpt&0x08))
1228                                                 {
1229                                                         currentConfig.PsndRate =  11025;
1230                                                         currentConfig.PicoOpt |=  8;
1231                                                 } else if ((inp & BTN_LEFT) && currentConfig.PsndRate == 11025 &&
1232                                                                 (currentConfig.PicoOpt&0x08) && !(PicoMCD&1))
1233                                                 {
1234                                                         currentConfig.PsndRate =  44100;
1235                                                         currentConfig.PicoOpt &= ~8;
1236                                                 } else
1237                                                         currentConfig.PsndRate = sndrate_prevnext(currentConfig.PsndRate, inp & BTN_RIGHT);
1238                                                 break;
1239                                         case MA_OPT_REGION:
1240                                                 region_prevnext(inp & BTN_RIGHT);
1241                                                 break;
1242                                         case MA_OPT_CONFIRM_STATES: {
1243                                                          int n = ((currentConfig.EmuOpt>>9)&1) | ((currentConfig.EmuOpt>>10)&2);
1244                                                          n += (inp & BTN_LEFT) ? -1 : 1;
1245                                                          if (n < 0) n = 0; else if (n > 3) n = 3;
1246                                                          n |= n << 1; n &= ~2;
1247                                                          currentConfig.EmuOpt &= ~0xa00;
1248                                                          currentConfig.EmuOpt |= n << 9;
1249                                                          break;
1250                                                  }
1251                                         case MA_OPT_SAVE_SLOT:
1252                                                  if (inp & BTN_RIGHT) {
1253                                                          state_slot++; if (state_slot > 9) state_slot = 0;
1254                                                  } else {state_slot--; if (state_slot < 0) state_slot = 9;
1255                                                  }
1256                                                  break;
1257                                         case MA_OPT_CPU_CLOCKS:
1258                                                  while ((inp = psp_pad_read(0)) & (BTN_LEFT|BTN_RIGHT)) {
1259                                                          currentConfig.CPUclock += (inp & BTN_LEFT) ? -1 : 1;
1260                                                          if (currentConfig.CPUclock <  19) currentConfig.CPUclock = 19;
1261                                                          if (currentConfig.CPUclock > 333) currentConfig.CPUclock = 333;
1262                                                          draw_menu_options(menu_sel); // will wait vsync
1263                                                  }
1264                                                  break;
1265                                         case MA_OPT_SAVECFG:
1266                                         case MA_OPT_SAVECFG_GAME:
1267                                         case MA_OPT_LOADCFG:
1268                                                  config_slot += (inp&BTN_RIGHT) ? 1 : -1;
1269                                                  if (config_slot > 9) config_slot = 0;
1270                                                  if (config_slot < 0) config_slot = 9;
1271                                                  me_enable(opt_entries, OPT_ENTRY_COUNT, MA_OPT_LOADCFG, config_slot != config_slot_current);
1272                                                  menu_sel_max = me_count_enabled(opt_entries, OPT_ENTRY_COUNT) - 1;
1273                                                  if (menu_sel > menu_sel_max) menu_sel = menu_sel_max;
1274                                                  break;
1275                                         default:
1276                                                 //lprintf("%s: something unknown selected (%i)\n", __FUNCTION__, selected_id);
1277                                                 break;
1278                                 }
1279                         }
1280                 }
1281                 if (inp & BTN_X) {
1282                         if (!me_process(opt_entries, OPT_ENTRY_COUNT, selected_id, 1))
1283                         {
1284                                 switch (selected_id)
1285                                 {
1286                                         case MA_OPT_SCD_OPTS:
1287                                                 cd_menu_loop_options();
1288                                                 if (engineState == PGS_ReloadRom)
1289                                                         return 0; // test BIOS
1290                                                 break;
1291                                         case MA_OPT_ADV_OPTS:
1292                                                 amenu_loop_options();
1293                                                 break;
1294                                         case MA_OPT_SAVECFG: // done (update and write)
1295                                                 menu_options_save();
1296                                                 if (emu_WriteConfig(0)) strcpy(menuErrorMsg, "config saved");
1297                                                 else strcpy(menuErrorMsg, "failed to write config");
1298                                                 return 1;
1299                                         case MA_OPT_SAVECFG_GAME: // done (update and write for current game)
1300                                                 menu_options_save();
1301                                                 if (emu_WriteConfig(1)) strcpy(menuErrorMsg, "config saved");
1302                                                 else strcpy(menuErrorMsg, "failed to write config");
1303                                                 return 1;
1304                                         case MA_OPT_LOADCFG:
1305                                                 ret = emu_ReadConfig(1, 1);
1306                                                 if (!ret) ret = emu_ReadConfig(0, 1);
1307                                                 if (ret)  strcpy(menuErrorMsg, "config loaded");
1308                                                 else      strcpy(menuErrorMsg, "failed to load config");
1309                                                 return 1;
1310                                         default:
1311                                                 //lprintf("%s: something unknown selected (%i)\n", __FUNCTION__, selected_id);
1312                                                 break;
1313                                 }
1314                         }
1315                 }
1316                 if(inp & BTN_CIRCLE) {
1317                         menu_options_save();
1318                         return 0;  // done (update, no write)
1319                 }
1320         }
1321 }
1322
1323 // -------------- credits --------------
1324
1325 static void draw_menu_credits(void)
1326 {
1327         int tl_x = 80+15, tl_y = 16+64, y;
1328         menu_draw_begin();
1329
1330         text_out16(tl_x, 16+20, "PicoDrive v" VERSION " (c) notaz, 2006,2007");
1331
1332         y = tl_y;
1333         text_out16(tl_x, y, "Credits:");
1334         text_out16(tl_x, (y+=10), "Dave: base code of PicoDrive");
1335         text_out16(tl_x, (y+=10), "MAME devs: YM2612 and SN76496 cores");
1336         text_out16(tl_x, (y+=10), "Charles MacDonald: Genesis hw docs");
1337         text_out16(tl_x, (y+=10), "Stephane Dallongeville:");
1338         text_out16(tl_x, (y+=10), "      opensource Gens");
1339         text_out16(tl_x, (y+=10), "Haze: Genesis hw info");
1340         text_out16(tl_x, (y+=10), "ketchupgun: skin design");
1341
1342         menu_draw_end();
1343 }
1344
1345
1346 // -------------- root menu --------------
1347
1348 menu_entry main_entries[] =
1349 {
1350         { "Resume game",        MB_NONE, MA_MAIN_RESUME_GAME, NULL, 0, 0, 0, 0 },
1351         { "Save State",         MB_NONE, MA_MAIN_SAVE_STATE,  NULL, 0, 0, 0, 0 },
1352         { "Load State",         MB_NONE, MA_MAIN_LOAD_STATE,  NULL, 0, 0, 0, 0 },
1353         { "Reset game",         MB_NONE, MA_MAIN_RESET_GAME,  NULL, 0, 0, 0, 0 },
1354         { "Load new ROM/ISO",   MB_NONE, MA_MAIN_LOAD_ROM,    NULL, 0, 0, 0, 1 },
1355         { "Change options",     MB_NONE, MA_MAIN_OPTIONS,     NULL, 0, 0, 0, 1 },
1356         { "Configure controls", MB_NONE, MA_MAIN_CONTROLS,    NULL, 0, 0, 0, 1 },
1357         { "Credits",            MB_NONE, MA_MAIN_CREDITS,     NULL, 0, 0, 0, 1 },
1358         { "Patches / GameGenie",MB_NONE, MA_MAIN_PATCHES,     NULL, 0, 0, 0, 0 },
1359         { "Exit",               MB_NONE, MA_MAIN_EXIT,        NULL, 0, 0, 0, 1 }
1360 };
1361
1362 #define MAIN_ENTRY_COUNT (sizeof(main_entries) / sizeof(main_entries[0]))
1363
1364 static void draw_menu_root(int menu_sel)
1365 {
1366         const int tl_x = 80+70, tl_y = 16+70;
1367
1368         menu_draw_begin();
1369
1370         text_out16(tl_x, 16+20, "PicoDrive v" VERSION);
1371
1372         menu_draw_selection(tl_x - 16, tl_y + menu_sel*10, 146);
1373
1374         me_draw(main_entries, MAIN_ENTRY_COUNT, tl_x, tl_y, NULL, NULL);
1375
1376         // error
1377         if (menuErrorMsg[0]) {
1378                 // memset((char *)menu_screen + 321*224*2, 0, 321*16*2);
1379                 text_out16(5, 258, menuErrorMsg);
1380         }
1381         menu_draw_end();
1382 }
1383
1384
1385 static void menu_loop_root(void)
1386 {
1387         static int menu_sel = 0;
1388         int ret, menu_sel_max;
1389         unsigned long inp = 0;
1390
1391         me_enable(main_entries, MAIN_ENTRY_COUNT, MA_MAIN_RESUME_GAME, rom_data != NULL);
1392         me_enable(main_entries, MAIN_ENTRY_COUNT, MA_MAIN_SAVE_STATE,  rom_data != NULL);
1393         me_enable(main_entries, MAIN_ENTRY_COUNT, MA_MAIN_LOAD_STATE,  rom_data != NULL);
1394         me_enable(main_entries, MAIN_ENTRY_COUNT, MA_MAIN_RESET_GAME,  rom_data != NULL);
1395         me_enable(main_entries, MAIN_ENTRY_COUNT, MA_MAIN_PATCHES,     PicoPatches != NULL);
1396
1397         menu_sel_max = me_count_enabled(main_entries, MAIN_ENTRY_COUNT) - 1;
1398         if (menu_sel > menu_sel_max) menu_sel = menu_sel_max;
1399
1400         /* make sure action buttons are not pressed on entering menu */
1401         draw_menu_root(menu_sel);
1402
1403         while (psp_pad_read(1) & (BTN_X|BTN_CIRCLE|BTN_SELECT)) psp_msleep(50);
1404
1405         for (;;)
1406         {
1407                 draw_menu_root(menu_sel);
1408                 inp = wait_for_input(BTN_UP|BTN_DOWN|BTN_X|BTN_CIRCLE|BTN_SELECT|BTN_L|BTN_R);
1409                 if(inp & BTN_UP  )  { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; }
1410                 if(inp & BTN_DOWN)  { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; }
1411                 if((inp & (BTN_L|BTN_R)) == (BTN_L|BTN_R)) debug_menu_loop();
1412                 if( inp & (BTN_SELECT|BTN_CIRCLE)) {
1413                         if (rom_data) {
1414                                 while (psp_pad_read(1) & (BTN_SELECT|BTN_CIRCLE)) psp_msleep(50); // wait until released
1415                                 engineState = PGS_Running;
1416                                 break;
1417                         }
1418                 }
1419                 if(inp & BTN_X)  {
1420                         switch (me_index2id(main_entries, MAIN_ENTRY_COUNT, menu_sel))
1421                         {
1422                                 case MA_MAIN_RESUME_GAME:
1423                                         if (rom_data) {
1424                                                 while (psp_pad_read(1) & BTN_X) psp_msleep(50);
1425                                                 engineState = PGS_Running;
1426                                                 return;
1427                                         }
1428                                         break;
1429                                 case MA_MAIN_SAVE_STATE:
1430                                         if (rom_data) {
1431                                                 if(savestate_menu_loop(0))
1432                                                         continue;
1433                                                 engineState = PGS_Running;
1434                                                 return;
1435                                         }
1436                                         break;
1437                                 case MA_MAIN_LOAD_STATE:
1438                                         if (rom_data) {
1439                                                 if(savestate_menu_loop(1))
1440                                                         continue;
1441                                                 engineState = PGS_Running;
1442                                                 return;
1443                                         }
1444                                         break;
1445                                 case MA_MAIN_RESET_GAME:
1446                                         if (rom_data) {
1447                                                 emu_ResetGame();
1448                                                 engineState = PGS_Running;
1449                                                 return;
1450                                         }
1451                                         break;
1452                                 case MA_MAIN_LOAD_ROM:
1453                                 {
1454                                         char curr_path[PATH_MAX], *selfname;
1455                                         FILE *tstf;
1456                                         if ( (tstf = fopen(currentConfig.lastRomFile, "rb")) )
1457                                         {
1458                                                 fclose(tstf);
1459                                                 strcpy(curr_path, currentConfig.lastRomFile);
1460                                         }
1461                                         else
1462                                                 getcwd(curr_path, PATH_MAX);
1463                                         selfname = romsel_loop(curr_path);
1464                                         if (selfname) {
1465                                                 lprintf("selected file: %s\n", selfname);
1466                                                 engineState = PGS_ReloadRom;
1467                                                 return;
1468                                         }
1469                                         break;
1470                                 }
1471                                 case MA_MAIN_OPTIONS:
1472                                         ret = menu_loop_options();
1473                                         if (ret == 1) continue; // status update
1474                                         if (engineState == PGS_ReloadRom)
1475                                                 return; // BIOS test
1476                                         break;
1477                                 case MA_MAIN_CONTROLS:
1478                                         kc_sel_loop();
1479                                         break;
1480                                 case MA_MAIN_CREDITS:
1481                                         draw_menu_credits();
1482                                         psp_msleep(500);
1483                                         inp = wait_for_input(BTN_X|BTN_CIRCLE);
1484                                         break;
1485                                 case MA_MAIN_EXIT:
1486                                         engineState = PGS_Quit;
1487                                         return;
1488                                 case MA_MAIN_PATCHES:
1489                                         if (rom_data && PicoPatches) {
1490                                                 patches_menu_loop();
1491                                                 PicoPatchApply();
1492                                                 strcpy(menuErrorMsg, "Patches applied");
1493                                                 continue;
1494                                         }
1495                                         break;
1496                                 default:
1497                                         lprintf("%s: something unknown selected\n", __FUNCTION__);
1498                                         break;
1499                         }
1500                 }
1501                 menuErrorMsg[0] = 0; // clear error msg
1502         }
1503 }
1504
1505 // warning: alignment
1506 static void menu_darken_bg(void *dst, const void *src, int pixels, int darker)
1507 {
1508         unsigned int *dest = dst;
1509         const unsigned int *srce = src;
1510         pixels /= 2;
1511         if (darker)
1512         {
1513                 while (pixels--)
1514                 {
1515                         unsigned int p = *srce++;
1516                         *dest++ = ((p&0xf79ef79e)>>1) - ((p&0xc618c618)>>3);
1517                 }
1518         }
1519         else
1520         {
1521                 while (pixels--)
1522                 {
1523                         unsigned int p = *srce++;
1524                         *dest++ = (p&0xf79ef79e)>>1;
1525                 }
1526         }
1527 }
1528
1529 static void menu_prepare_bg(int use_game_bg)
1530 {
1531         memset(bg_buffer, 0, sizeof(bg_buffer));
1532
1533         if (use_game_bg)
1534         {
1535                 // darken the active framebuffer
1536                 /*
1537                 memset(bg_buffer, 0, 321*8*2);
1538                 menu_darken_bg(bg_buffer + 321*8*2, (char *)giz_screen + 321*8*2, 321*224, 1);
1539                 memset(bg_buffer + 321*232*2, 0, 321*8*2);
1540                 */
1541         }
1542         else
1543         {
1544                 // should really only happen once, on startup..
1545                 readpng(bg_buffer, "skin/background.png", READPNG_BG);
1546         }
1547 }
1548
1549 static void menu_gfx_prepare(void)
1550 {
1551         menu_prepare_bg(rom_data != NULL);
1552
1553         menu_draw_begin();
1554         menu_draw_end();
1555 }
1556
1557
1558 void menu_loop(void)
1559 {
1560         menu_gfx_prepare();
1561
1562         menu_loop_root();
1563
1564         menuErrorMsg[0] = 0;
1565 }
1566
1567
1568 // --------- CD tray close menu ----------
1569
1570 static void draw_menu_tray(int menu_sel)
1571 {
1572         int tl_x = 70, tl_y = 90, y;
1573
1574         menu_draw_begin();
1575
1576         text_out16(tl_x, 20, "The unit is about to");
1577         text_out16(tl_x, 30, "close the CD tray.");
1578
1579         y = tl_y;
1580         text_out16(tl_x, y,       "Load new CD image");
1581         text_out16(tl_x, (y+=10), "Insert nothing");
1582
1583         // draw cursor
1584         text_out16(tl_x - 16, tl_y + menu_sel*10, ">");
1585         // error
1586         if (menuErrorMsg[0]) text_out16(5, 226, menuErrorMsg);
1587         menu_draw_end();
1588 }
1589
1590
1591 int menu_loop_tray(void)
1592 {
1593         int menu_sel = 0, menu_sel_max = 1;
1594         unsigned long inp = 0;
1595         char curr_path[PATH_MAX], *selfname;
1596         FILE *tstf;
1597
1598         menu_gfx_prepare();
1599
1600         if ( (tstf = fopen(currentConfig.lastRomFile, "rb")) )
1601         {
1602                 fclose(tstf);
1603                 strcpy(curr_path, currentConfig.lastRomFile);
1604         }
1605         else
1606         {
1607                 getcwd(curr_path, PATH_MAX);
1608         }
1609
1610         /* make sure action buttons are not pressed on entering menu */
1611         draw_menu_tray(menu_sel);
1612         while (psp_pad_read(1) & BTN_X) psp_msleep(50);
1613
1614         for (;;)
1615         {
1616                 draw_menu_tray(menu_sel);
1617                 inp = wait_for_input(BTN_UP|BTN_DOWN|BTN_X);
1618                 if(inp & BTN_UP  )  { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; }
1619                 if(inp & BTN_DOWN)  { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; }
1620                 if(inp & BTN_X   )  {
1621                         switch (menu_sel) {
1622                                 case 0: // select image
1623                                         selfname = romsel_loop(curr_path);
1624                                         if (selfname) {
1625                                                 int ret = -1, cd_type;
1626                                                 cd_type = emu_cdCheck(NULL);
1627                                                 if (cd_type > 0)
1628                                                         ret = Insert_CD(romFileName, cd_type == 2);
1629                                                 if (ret != 0) {
1630                                                         sprintf(menuErrorMsg, "Load failed, invalid CD image?");
1631                                                         lprintf("%s\n", menuErrorMsg);
1632                                                         continue;
1633                                                 }
1634                                                 engineState = PGS_RestartRun;
1635                                                 return 1;
1636                                         }
1637                                         break;
1638                                 case 1: // insert nothing
1639                                         engineState = PGS_RestartRun;
1640                                         return 0;
1641                         }
1642                 }
1643                 menuErrorMsg[0] = 0; // clear error msg
1644         }
1645 }
1646
1647