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