e3c28a43b743a0f941a5ca3b53abdcfb4695ced8
[picodrive.git] / platform / common / menu_pico.c
1 /*
2  * PicoDrive
3  * (C) notaz, 2010,2011
4  * (C) irixxxx, 2023,2024
5  *
6  * This work is licensed under the terms of MAME license.
7  * See COPYING file in the top-level directory.
8  */
9 #include <stdio.h>
10 #include <string.h>
11 #include <time.h>
12
13 #include "emu.h"
14 #include "menu_pico.h"
15 #include "input_pico.h"
16 #include "keyboard.h"
17 #include "version.h"
18
19 #include "../libpicofe/plat.h"
20
21 #include <pico/pico_int.h>
22 #include <pico/patch.h>
23
24 #if defined(PANDORA) || defined(__PS2__)
25 #define MENU_X2 1
26 #else
27 #define MENU_X2 0
28 #endif
29
30 #define COL_ROM PXMAKE(0xbf, 0xbf, 0xff)
31 #define COL_OTH PXMAKE(0xaf, 0xff, 0xaf)
32
33 // FIXME
34 #ifndef REVISION
35 #define REVISION "0"
36 #endif
37
38 static const char *rom_exts[] = {
39         "zip", "bin",
40         "pco", "smd", "gen", "md",
41         "iso", "cso", "cue", "chd",
42         "32x",
43         "sms", "gg",  "sg", "sc",
44         NULL
45 };
46
47 // rrrr rggg gggb bbbb
48 static unsigned short fname2color(const char *fname)
49 {
50         static const char *other_exts[] = { "gmv", "pat" };
51         const char *ext;
52         int i;
53
54         ext = strrchr(fname, '.');
55         if (ext++ == NULL) {
56                 ext = fname + strlen(fname) - 3;
57                 if (ext < fname) ext = fname;
58         }
59
60         for (i = 0; rom_exts[i] != NULL; i++)
61                 if (strcasecmp(ext, rom_exts[i]) == 0) return COL_ROM;
62         for (i = 0; i < array_size(other_exts); i++)
63                 if (strcasecmp(ext, other_exts[i]) == 0) return COL_OTH;
64         return PXMAKE(0xff, 0xff, 0xff);
65 }
66
67 #include <platform/libpicofe/menu.c>
68
69 static const char *men_dummy[] = { NULL };
70 static int menu_w, menu_h;
71
72 /* platform specific options and handlers */
73 #if   defined(__GP2X__)
74 #include <platform/gp2x/menu.c>
75 #elif defined(__PSP__)
76 #include <platform/psp/menu.c>
77 #elif defined(__PS2__)
78 #include <platform/ps2/menu.c>
79 #elif defined(PANDORA)
80 #include <platform/pandora/menu.c>
81 #else
82 #include <platform/linux/menu.c>
83 #endif
84
85 static void make_bg(int no_scale, int from_screen)
86 {
87         unsigned short *src = (void *)g_menubg_src_ptr;
88         int w = g_menubg_src_w ? g_menubg_src_w : g_screen_width;
89         int h = g_menubg_src_h ? g_menubg_src_h : g_screen_height;
90         int pp = g_menubg_src_pp ? g_menubg_src_pp : g_screen_ppitch;
91         short *dst;
92
93         if (from_screen) {
94                 src = g_screen_ptr;
95                 w = g_screen_width;
96                 h = g_screen_height;
97                 pp = g_screen_ppitch;
98         }
99
100         memset(g_menubg_ptr, 0, g_menuscreen_w * g_menuscreen_h * 2);
101         if (src == NULL)
102                 return;
103
104         if (!no_scale && g_menuscreen_w / w >= 2 && g_menuscreen_h / h >= 2)
105         {
106                 int xf = g_menuscreen_w / w, yf = g_menuscreen_h / h;
107                 int f = no_scale ? 1 : xf < yf ? xf : yf, xs = f * w, ys = f * h;
108                 int x = (g_menuscreen_w - xs)/2, y = (g_menuscreen_h - ys)/2;
109                 uint16_t *p = (uint16_t *)g_menubg_ptr;
110                 uint16_t *q = (uint16_t *)src;
111
112                 int i, j, k, l;
113                 p += y * g_menuscreen_pp + x;
114                 for (i = 0; i < h; i++) {
115                         for (j = 0; j < w; j++, q++) {
116                                 uint16_t t = (PXMASKH(*q,1)>>1) - (PXMASKH(*q,3)>>3);
117                                 for (l = 0; l < f; l++)
118                                         *p++ = t;
119                         }
120                         p += g_menuscreen_pp - xs;
121                         q += pp - w;
122                         for (k = 1; k < f; k++) {
123                                 memcpy(p, p-g_menuscreen_pp, g_menuscreen_w*2);
124                                 p += g_menuscreen_pp;
125                         }
126                 }
127                 return;
128         }
129
130         if (w > g_menuscreen_w)
131                 w = g_menuscreen_w;
132         if (h > g_menuscreen_h)
133                 h = g_menuscreen_h;
134         dst = (short *)g_menubg_ptr +
135                 (g_menuscreen_h / 2 - h / 2) * g_menuscreen_w +
136                 (g_menuscreen_w / 2 - w / 2);
137
138         // darken the active framebuffer
139         for (; h > 0; dst += g_menuscreen_w, src += pp, h--)
140                 menu_darken_bg(dst, src, w, 1);
141 }
142
143 static void copy_bg(int dir)
144 {
145         unsigned short *bg = (void *)g_menubg_ptr;
146         unsigned short *sc = (void *)g_menuscreen_ptr;
147         int h = g_menuscreen_h;
148
149         for (; h > 0; sc += g_menuscreen_pp, bg += g_menuscreen_w, h--) {
150                 if (dir)
151                         memcpy(bg, sc, g_menuscreen_w * 2);
152                 else
153                         memcpy(sc, bg, g_menuscreen_w * 2);
154         }
155 }
156
157 static void menu_draw_prep(void)
158 {
159         plat_video_menu_update();
160
161         if (menu_w == g_menuscreen_w && menu_h == g_menuscreen_h)
162                 return;
163         menu_w = g_menuscreen_w, menu_h = g_menuscreen_h;
164
165         if (PicoGameLoaded)
166         {
167                 make_bg(0, 0);
168         }
169         else
170         {
171                 int pos;
172                 char buff[256];
173                 pos = plat_get_skin_dir(buff, 256);
174                 strcpy(buff + pos, "background.png");
175
176                 // should really only happen once, on startup..
177                 memset(g_menubg_ptr, 0, g_menuscreen_w * g_menuscreen_h * 2);
178                 if (readpng(g_menubg_ptr, buff, READPNG_BG,
179                                                 g_menuscreen_w, g_menuscreen_h) < 0)
180                         memset(g_menubg_ptr, 0, g_menuscreen_w * g_menuscreen_h * 2);
181         }
182 }
183
184 static void draw_savestate_bg(int slot)
185 {
186         const char *fname;
187         void *tmp_state;
188
189         fname = emu_get_save_fname(1, 0, slot, NULL);
190         if (!fname)
191                 return;
192
193         tmp_state = PicoTmpStateSave();
194
195         PicoStateLoadGfx(fname);
196
197         /* do a frame and fetch menu bg */
198         pemu_forced_frame(0, 0);
199
200         make_bg(0, 1);
201
202         PicoTmpStateRestore(tmp_state);
203 }
204
205 static void menu_enter(int is_rom_loaded)
206 {
207         plat_video_menu_enter(is_rom_loaded);
208         menu_w = menu_h = 0;
209         menu_draw_prep();
210 }
211
212 // --------- loading ROM screen ----------
213
214 static int cdload_called = 0;
215
216 static void load_progress_cb(int percent)
217 {
218         int ln, len = percent * g_menuscreen_w / 100;
219         unsigned short *dst;
220
221         if (len > g_menuscreen_w)
222                 len = g_menuscreen_w;
223
224         menu_draw_begin(0, 1);
225         copy_bg(0);
226         dst = (unsigned short *)g_menuscreen_ptr + g_menuscreen_pp * me_sfont_h * 2;
227         for (ln = me_sfont_h - 2; ln > 0; ln--, dst += g_menuscreen_pp)
228                 memset(dst, 0xff, len * 2);
229         menu_draw_end();
230 }
231
232 static void cdload_progress_cb(const char *fname, int percent)
233 {
234         int ln, len = percent * g_menuscreen_w / 100;
235         unsigned short *dst;
236
237         menu_draw_begin(0, 1);
238         dst = (unsigned short *)g_menuscreen_ptr + g_menuscreen_pp * me_sfont_h * 2;
239
240         copy_bg(0);
241         menuscreen_memset_lines(dst, 0xff, me_sfont_h - 2);
242
243         smalltext_out16(1, 3 * me_sfont_h, "Processing CD image / MP3s", PXMAKE(0xff, 0xff, 0xff));
244         smalltext_out16(1, 4 * me_sfont_h, fname, PXMAKE(0xff, 0xff, 0xff));
245         dst += g_menuscreen_pp * me_sfont_h * 3;
246
247         if (len > g_menuscreen_w)
248                 len = g_menuscreen_w;
249
250         for (ln = (me_sfont_h - 2); ln > 0; ln--, dst += g_menuscreen_pp)
251                 memset(dst, 0xff, len * 2);
252         menu_draw_end();
253
254         cdload_called = 1;
255 }
256
257 void menu_romload_prepare(const char *rom_name)
258 {
259         const char *p = rom_name + strlen(rom_name);
260
261         while (p > rom_name && *p != '/')
262                 p--;
263
264         menu_draw_begin(1, 1);
265         smalltext_out16(1, 1, "Loading", PXMAKE(0xff, 0xff, 0xff));
266         smalltext_out16(1, me_sfont_h, p, PXMAKE(0xff, 0xff, 0xff));
267         /* copy menu to bg for callbacks. OK since we are not in menu_loop here */
268         copy_bg(1);
269         menu_draw_end();
270
271         PicoCartLoadProgressCB = load_progress_cb;
272         PicoCDLoadProgressCB = cdload_progress_cb;
273         cdload_called = 0;
274 }
275
276 void menu_romload_end(void)
277 {
278         PicoCartLoadProgressCB = NULL;
279         PicoCDLoadProgressCB = NULL;
280
281         menu_draw_begin(0, 1);
282         copy_bg(0);
283         smalltext_out16(1, (cdload_called ? 6 : 3) * me_sfont_h,
284                 "Starting emulation...", PXMAKE(0xff, 0xff, 0xff));
285         menu_draw_end();
286 }
287
288 // ------------ patch/gg menu ------------
289
290 static void draw_patchlist(int sel)
291 {
292         int max_cnt, start, i, pos, active;
293
294         max_cnt = g_menuscreen_h / me_sfont_h;
295         start = max_cnt / 2 - sel;
296
297         menu_draw_begin(1, 0);
298
299         for (i = 0; i < PicoPatchCount; i++) {
300                 pos = start + i;
301                 if (pos < 0) continue;
302                 if (pos >= max_cnt) break;
303                 active = PicoPatches[i].active;
304                 smalltext_out16(14,                pos * me_sfont_h, active ? "ON " : "OFF", PXMAKE(0xff, 0xff, active ? 0xff : 0xb0));
305                 smalltext_out16(14 + me_sfont_w*4, pos * me_sfont_h, PicoPatches[i].name,    PXMAKE(0xff, 0xff, active ? 0xff : 0xb0));
306         }
307         pos = start + i;
308         if (pos < max_cnt)
309                 smalltext_out16(14, pos * me_sfont_h, "done", PXMAKE(0xff, 0xff, 0xff));
310
311         text_out16(5, max_cnt / 2 * me_sfont_h, ">");
312         menu_draw_end();
313 }
314
315 static void menu_loop_patches(void)
316 {
317         static int menu_sel = 0;
318         int inp;
319
320         for (;;)
321         {
322                 draw_patchlist(menu_sel);
323                 inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT|PBTN_L|PBTN_R
324                                 |PBTN_MOK|PBTN_MBACK, NULL, 33);
325                 if (inp & PBTN_UP  ) { menu_sel--; if (menu_sel < 0) menu_sel = PicoPatchCount; }
326                 if (inp & PBTN_DOWN) { menu_sel++; if (menu_sel > PicoPatchCount) menu_sel = 0; }
327                 if (inp &(PBTN_LEFT|PBTN_L))  { menu_sel-=10; if (menu_sel < 0) menu_sel = 0; }
328                 if (inp &(PBTN_RIGHT|PBTN_R)) { menu_sel+=10; if (menu_sel > PicoPatchCount) menu_sel = PicoPatchCount; }
329                 if (inp & PBTN_MOK) { // action
330                         if (menu_sel < PicoPatchCount)
331                                 PicoPatches[menu_sel].active = !PicoPatches[menu_sel].active;
332                         else    break;
333                 }
334                 if (inp & PBTN_MBACK)
335                         break;
336         }
337 }
338
339 // -------------- key config --------------
340
341 // PicoIn.pad[] format: MXYZ SACB RLDU
342 me_bind_action me_ctrl_actions[] =
343 {
344         { "UP     ", 0x0001 },
345         { "DOWN   ", 0x0002 },
346         { "LEFT   ", 0x0004 },
347         { "RIGHT  ", 0x0008 },
348         { "A      ", 0x0040 },
349         { "B      ", 0x0010 },
350         { "C      ", 0x0020 },
351         { "A turbo", 0x4000 },
352         { "B turbo", 0x1000 },
353         { "C turbo", 0x2000 },
354         { "START  ", 0x0080 },
355         { "MODE   ", 0x0800 },
356         { "X      ", 0x0400 },
357         { "Y      ", 0x0200 },
358         { "Z      ", 0x0100 },
359         { NULL,      0 },
360 };
361
362 me_bind_action emuctrl_actions[] =
363 {
364         { "Load State     ", PEV_STATE_LOAD },
365         { "Save State     ", PEV_STATE_SAVE },
366         { "Prev Save Slot ", PEV_SSLOT_PREV },
367         { "Next Save Slot ", PEV_SSLOT_NEXT },
368         { "Switch Renderer", PEV_SWITCH_RND },
369         { "Volume Down    ", PEV_VOL_DOWN },
370         { "Volume Up      ", PEV_VOL_UP },
371         { "Fast forward   ", PEV_FF },
372         { "Reset Game     ", PEV_RESET },
373         { "Enter Menu     ", PEV_MENU },
374         { "Pico Prev page ", PEV_PICO_PPREV },
375         { "Pico Next page ", PEV_PICO_PNEXT },
376         { "Pico Storyware ", PEV_PICO_STORY },
377         { "Pico Pad       ", PEV_PICO_PAD },
378         { "Switch Keyboard", PEV_SWITCH_KBD },
379         { "Capture Mouse  ", PEV_GRAB_INPUT },
380         { NULL,                0 }
381 };
382
383 static int key_config_loop_wrap(int id, int keys)
384 {
385         int n;
386
387         switch (id) {
388                 case MA_CTRL_PLAYER1:
389                         key_config_loop(me_ctrl_actions, array_size(me_ctrl_actions) - 1, 0);
390                         break;
391                 case MA_CTRL_PLAYER2:
392                         key_config_loop(me_ctrl_actions, array_size(me_ctrl_actions) - 1, 1);
393                         break;
394                 case MA_CTRL_PLAYER3:
395                         key_config_loop(me_ctrl_actions, array_size(me_ctrl_actions) - 1, 2);
396                         break;
397                 case MA_CTRL_PLAYER4:
398                         key_config_loop(me_ctrl_actions, array_size(me_ctrl_actions) - 1, 3);
399                         break;
400                 case MA_CTRL_EMU:
401                         n = (plat_has_wm() ? 1 : 2);
402                         key_config_loop(emuctrl_actions, array_size(emuctrl_actions) - n, -1);
403                         break;
404                 default:
405                         break;
406         }
407         return 0;
408 }
409
410 static const char *mgn_dev_name(int id, int *offs)
411 {
412         const char *name = NULL;
413         static int it = 0;
414
415         if (id == MA_CTRL_DEV_FIRST)
416                 it = 0;
417
418         for (; it < IN_MAX_DEVS; it++) {
419                 name = in_get_dev_name(it, 1, 1);
420                 if (name != NULL)
421                         break;
422         }
423
424         it++;
425         return name;
426 }
427
428 static void kbd_draw(struct key *desc[], int shift, int xoffs, int yoffs, struct key *hi)
429 {
430         int i, j;
431         struct key *key;
432
433         if (g_menuscreen_w >= (MENU_X2 ? 960 : 480))
434                 xoffs -= 50;
435         for (i = 0; desc[i]; i++) {
436                 for (j = 0, key = &desc[i][j]; key->lower; j++, key++) {
437                         int color = (key != hi ? PXMAKE(0xa0, 0xa0, 0xa0) :
438                                                  PXMAKE(0xff, 0xff, 0xff));
439                         char *text = (shift ? key->upper : key->lower);
440                         if (g_menuscreen_w >= (MENU_X2 ? 960 : 480))
441                         text_out16_(xoffs + key->xpos*me_mfont_w, yoffs + i*me_mfont_h, text, color);
442                         else
443                         smalltext_out16(xoffs + key->xpos*me_sfont_w, yoffs + i*me_sfont_h, text, color);
444                 }
445         }
446 }
447
448 int key_config_kbd_loop(int id, int keys)
449 {
450         struct key **kbd = kbd_pico;
451         int keyx = 0, keyy = 0, shift = 0, toggle = 0;
452         char mok[20], ma2[20], r[20], l[20];
453         int inp;
454         int dev;
455
456         int w = 20 * me_mfont_w / 2;
457         int x = g_menuscreen_w / 2;
458
459         struct key *key;
460         const int *binds;
461         int bc;
462
463         snprintf(mok, sizeof(mok), "%s", in_get_key_name(-1, -PBTN_MOK));
464         snprintf(ma2, sizeof(ma2), "%s", in_get_key_name(-1, -PBTN_MA2));
465         snprintf(r, sizeof(r), "%s", in_get_key_name(-1, -PBTN_R));
466         snprintf(l, sizeof(r), "%s", in_get_key_name(-1, -PBTN_L));
467
468         for (dev = 0; dev < IN_MAX_DEVS-1; dev++)
469                 if ((binds = in_get_dev_kbd_binds(dev))) break;
470
471         while (in_menu_wait_any(NULL, 50) & (PBTN_MOK|PBTN_MBACK|PBTN_MENU))
472                 ;
473
474         for (;;) {
475                 key = &kbd[keyy][keyx];
476                 in_get_config(dev, IN_CFG_BIND_COUNT, &bc);
477                 for (bc--; bc >= 0 && binds[bc] != key->key; bc--) ;
478
479                 menu_draw_begin(1, 0);
480                 text_out16(x - w, 2 * me_mfont_h, "Keyboard type: %s", toggle ? "SC-3000" : "Pico");
481                 kbd_draw(kbd, shift, (g_menuscreen_w - 320)/2, 4 * me_mfont_h, key);
482                 text_out16(x - 2*w, g_menuscreen_h - 7 * me_mfont_h, "currently bound to %s",
483                                 bc >= 0 ? in_get_key_name(-1, bc) : "nothing");
484                 if (!shift)
485                         text_out16(x - 2*w, g_menuscreen_h - 5 * me_mfont_h, "%s - bind, %s - clear", mok, ma2);
486                 text_out16(x - 2*w, g_menuscreen_h - 4 * me_mfont_h, "%s - swap type, %s - shift view", r, l);
487                 menu_draw_end();
488
489                 inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT|PBTN_MA2|
490                         PBTN_MOK|PBTN_MBACK|PBTN_MENU|PBTN_L|PBTN_R, NULL, 70);
491                 if (inp & (PBTN_MENU|PBTN_MBACK))
492                         break;
493
494                 if (inp & PBTN_UP) {
495                         if (--keyy < 0) while (kbd[keyy+1]) keyy++;
496                         keyx = vkbd_find_xpos(kbd[keyy], key->xpos);
497                 }
498                 if (inp & PBTN_DOWN) {
499                         if (kbd[++keyy] == NULL) keyy = 0;
500                         keyx = vkbd_find_xpos(kbd[keyy], key->xpos);
501                 }
502                 if (inp & PBTN_LEFT) {
503                         if (--keyx < 0) while (kbd[keyy][keyx+1].lower) keyx++;
504                 }
505                 if (inp & PBTN_RIGHT) {
506                         if (kbd[keyy][++keyx].lower == NULL) keyx = 0;
507                 }
508                 if (inp & PBTN_L) {
509                         shift = !shift;
510                 }
511                 if (inp & PBTN_R) {
512                         toggle = !toggle;
513                         kbd = (toggle ? kbd_sc3000 : kbd_pico);
514                         keyx = vkbd_find_xpos(kbd[keyy], key->xpos);
515                 }
516
517                 if (inp & PBTN_MOK) {
518                         int is_down, bind_dev_id, kc;
519
520                         while (in_menu_wait_any(NULL, 30) & PBTN_MOK)
521                                 ;
522                         if (shift) continue;
523
524                         key = &kbd[keyy][keyx];
525                         menu_draw_begin(1, 0);
526                         text_out16(x - w, 2 * me_mfont_h, "Keyboard type: %s", toggle ? "SC-3000" : "Pico");
527                         kbd_draw(kbd, shift, (g_menuscreen_w - 320)/2, 4 * me_mfont_h, key);
528                         text_out16(x - 2*w, g_menuscreen_h - 4 * me_mfont_h, "Press a button to bind/unbind");
529                         menu_draw_end();
530
531                         /* wait for some up event */
532                         for (is_down = 1; is_down; )
533                                 kc = in_update_keycode(&bind_dev_id, &is_down, NULL, -1);
534
535                         in_bind_kbd_key(dev, bc, 0);
536                         in_bind_kbd_key(bind_dev_id, kc, kbd[keyy][keyx].key);
537                 }
538                 if (inp & PBTN_MA2) {
539                         in_bind_kbd_key(dev, bc, 0);
540                 }
541         }
542
543         return 0;
544 }
545
546
547 const char *indev0_names[] = { "none", "3 button pad", "6 button pad", "Mouse", "Team player", "4 way play", NULL };
548 const char *indev1_names[] = { "none", "3 button pad", "6 button pad", "Mouse", NULL };
549
550 static char h_play12[55];
551 static char h_kbd[55];
552 static char h_play34[] = "Works only for Mega Drive/CD/32X games having\n"
553                                 "support for Team player, 4 way play, or J-cart";
554
555 static char player[] = "Player 1";
556
557 static const char *mgn_nothing(int id, int *offs)
558 {
559         return "";
560 }
561
562 static int key_config_players(int id, int keys)
563 {
564         int pid = player[strlen(player)-1] - '0';
565
566         if (keys & PBTN_LEFT)
567                 if (--pid < 1) pid = 4;
568         if (keys & PBTN_RIGHT)
569                 if (++pid > 4) pid = 1;
570
571         player[strlen(player)-1] = pid + '0';
572         e_menu_keyconfig[0].help = (pid >= 3 ? h_play34 : h_play12);
573
574         if (keys & PBTN_MOK)
575                 key_config_loop(me_ctrl_actions, array_size(me_ctrl_actions) - 1, pid-1);
576
577         return 0;
578 }
579
580 static const char *mgn_keyboard(int id, int *offs)
581 {
582         static char *kbds[] = { "OFF", "virtual", "physical" };
583         if (currentConfig.keyboard < 0 || currentConfig.keyboard > 2)
584                 return kbds[0];
585         return kbds[currentConfig.keyboard];
586 }
587
588 static int key_config_keyboard(int id, int keys)
589 {
590         int kid = currentConfig.keyboard;
591 #ifdef USE_SDL  // TODO this info should come from platform!
592         int k = 2;
593 #else
594         int k = 1;
595 #endif
596
597         if (keys & PBTN_LEFT)
598                 if (--kid < 0) kid = k;
599         if (keys & PBTN_RIGHT)
600                 if (++kid > k) kid = 0;
601
602         currentConfig.keyboard = kid;
603
604         e_menu_keyconfig[2].help = (currentConfig.keyboard == 2 ? h_kbd : NULL);
605
606         if (keys & PBTN_MOK)
607                 if (currentConfig.keyboard == 2)
608                         key_config_kbd_loop(MA_CTRL_KEYBOARD, 0);
609
610         return 0;
611 }
612
613 static menu_entry e_menu_keyconfig[] =
614 {
615         mee_cust_nosave(player,             MA_CTRL_PLAYER1,    key_config_players, mgn_nothing),
616         mee_handler_id("Emulator hotkeys",  MA_CTRL_EMU,        key_config_loop_wrap),
617         mee_cust_h    ("Keyboard",          MA_CTRL_KEYBOARD,   key_config_keyboard, mgn_keyboard, h_kbd),
618         mee_enum      ("Input device 1",    MA_OPT_INPUT_DEV0,  currentConfig.input_dev0, indev0_names),
619         mee_enum      ("Input device 2",    MA_OPT_INPUT_DEV1,  currentConfig.input_dev1, indev1_names),
620         mee_range     ("Turbo rate",        MA_CTRL_TURBO_RATE, currentConfig.turbo_rate, 1, 30),
621         mee_range     ("Analog deadzone",   MA_CTRL_DEADZONE,   currentConfig.analog_deadzone, 1, 99),
622         mee_label     (""),
623         mee_label     ("Input devices:"),
624         mee_label_mk  (MA_CTRL_DEV_FIRST, mgn_dev_name),
625         mee_label_mk  (MA_CTRL_DEV_NEXT,  mgn_dev_name),
626         mee_label_mk  (MA_CTRL_DEV_NEXT,  mgn_dev_name),
627         mee_label_mk  (MA_CTRL_DEV_NEXT,  mgn_dev_name),
628         mee_label_mk  (MA_CTRL_DEV_NEXT,  mgn_dev_name),
629         mee_label_mk  (MA_CTRL_DEV_NEXT,  mgn_dev_name),
630         mee_label_mk  (MA_CTRL_DEV_NEXT,  mgn_dev_name),
631         mee_end,
632 };
633
634 static int menu_loop_keyconfig(int id, int keys)
635 {
636         static int sel = 0;
637         int it = 0, x = me_id2offset(e_menu_keyconfig, MA_CTRL_DEV_FIRST);
638
639         while (in_get_dev_name(it, 1, 1))
640                 it++;
641         for (it += x; x && e_menu_keyconfig[x].name; x++)
642                 e_menu_keyconfig[x].enabled = x < it;
643
644         char l[20], r[20];
645         snprintf(l,sizeof(l),"%s", in_get_key_name(-1, -PBTN_LEFT));
646         snprintf(r,sizeof(r),"%s", in_get_key_name(-1, -PBTN_RIGHT));
647         snprintf(h_play12, sizeof(h_play12), "%s/%s - select, %s - configure", l, r, in_get_key_name(-1, -PBTN_MOK));
648         snprintf(h_kbd, sizeof(h_kbd), "%s - configure", in_get_key_name(-1, -PBTN_MOK));
649
650         player[strlen(player)-1] = '1';
651         e_menu_keyconfig[0].help = h_play12;
652         e_menu_keyconfig[2].help = (currentConfig.keyboard == 2 ? h_kbd : NULL);
653
654         me_loop_d(e_menu_keyconfig, &sel, menu_draw_prep, NULL);
655
656         PicoSetInputDevice(0, currentConfig.input_dev0);
657         PicoSetInputDevice(1, currentConfig.input_dev1);
658
659         return 0;
660 }
661
662 // ------------ MD options menu ------------
663
664 static const char h_renderer[] = "16bit is more accurate, 8bit is faster";
665 static const char h_fmsound[]  = "Disabling improves performance, but breaks sound";
666 static const char h_dacnoise[] = "FM chips in the 1st Mega Drive model have DAC noise,\n"
667                                 "newer models used different chips without this";
668 static const char h_fmfilter[] = "Improves sound accuracy but is noticeably slower,\n"
669                                 "best quality if native rate isn't working";
670 static const char h_picopen[]  = "Enabling resets Pico display and d-pad input back to\n"
671                                 "screen if the Pico pen button is pressed";
672
673 static menu_entry e_menu_md_options[] =
674 {
675         mee_enum_h    ("Renderer",                  MA_OPT_RENDERER, currentConfig.renderer, renderer_names, h_renderer),
676         mee_onoff_h   ("FM audio",                  MA_OPT2_ENABLE_YM2612, PicoIn.opt, POPT_EN_FM, h_fmsound),
677         mee_onoff_h   ("FM filter",                 MA_OPT_FM_FILTER, PicoIn.opt, POPT_EN_FM_FILTER, h_fmfilter),
678         mee_onoff_h   ("FM DAC noise",              MA_OPT2_ENABLE_YM_DAC, PicoIn.opt, POPT_EN_FM_DAC, h_dacnoise),
679         mee_onoff_h   ("Pen button shows screen",   MA_OPT_PICO_PEN, currentConfig.EmuOpt, EOPT_PICO_PEN, h_picopen),
680         mee_end,
681 };
682
683 static int menu_loop_md_options(int id, int keys)
684 {
685         static int sel = 0;
686         if (renderer_names[0] == NULL)
687                 me_enable(e_menu_md_options, MA_OPT_RENDERER, 0);
688         me_loop_d(e_menu_md_options, &sel, menu_draw_prep, NULL);
689
690         return 0;
691 }
692
693 // ------------ SCD options menu ------------
694
695 static const char h_cdleds[] = "Show power/CD LEDs of emulated console";
696 static const char h_cdda[]   = "Play audio tracks from mp3s/wavs/bins";
697 static const char h_cdpcm[]  = "Emulate PCM audio chip for effects/voices/music";
698 static const char h_srcart[] = "Emulate the save RAM cartridge accessory\n"
699                                 "most games don't need this";
700
701 static menu_entry e_menu_cd_options[] =
702 {
703         mee_onoff_h("SaveRAM cart",         MA_CDOPT_SAVERAM,       PicoIn.opt, POPT_EN_MCD_RAMCART, h_srcart),
704         mee_onoff_h("CD LEDs",              MA_CDOPT_LEDS,          currentConfig.EmuOpt, EOPT_EN_CD_LEDS, h_cdleds),
705         mee_onoff_h("CDDA audio",           MA_CDOPT_CDDA,          PicoIn.opt, POPT_EN_MCD_CDDA, h_cdda),
706         mee_onoff_h("PCM audio",            MA_CDOPT_PCM,           PicoIn.opt, POPT_EN_MCD_PCM, h_cdpcm),
707         mee_end,
708 };
709
710 static int menu_loop_cd_options(int id, int keys)
711 {
712         static int sel = 0;
713         me_loop_d(e_menu_cd_options, &sel, menu_draw_prep, NULL);
714         return 0;
715 }
716
717 // ------------ 32X options menu ------------
718
719 #ifndef NO_32X
720
721 // convert from multiplier of VClk
722 static int mh_opt_sh2cycles(int id, int keys)
723 {
724         int *khz = (id == MA_32XOPT_MSH2_CYCLES) ?
725                 &currentConfig.msh2_khz : &currentConfig.ssh2_khz;
726
727         if (keys & (PBTN_LEFT|PBTN_RIGHT))
728                 *khz += (keys & PBTN_LEFT) ? -50 : 50;
729         if (keys & (PBTN_L|PBTN_R))
730                 *khz += (keys & PBTN_L) ? -500 : 500;
731
732         if (*khz < 1)
733                 *khz = 1;
734         else if (*khz > 0x7fffffff / 1000)
735                 *khz = 0x7fffffff / 1000;
736
737         Pico32xSetClocks(currentConfig.msh2_khz * 1000, currentConfig.ssh2_khz * 1000);
738
739         return 0;
740 }
741
742 static const char *mgn_opt_sh2cycles(int id, int *offs)
743 {
744         int khz = (id == MA_32XOPT_MSH2_CYCLES) ?
745                 currentConfig.msh2_khz : currentConfig.ssh2_khz;
746
747         sprintf(static_buff, "%d", khz);
748         return static_buff;
749 }
750
751 static const char h_pwm[]        = "Disabling may improve performance, but break sound";
752 static const char h_pwmopt[]     = "Enabling may improve performance, but break sound";
753
754 static menu_entry e_menu_32x_options[] =
755 {
756         mee_enum      ("32X renderer",      MA_32XOPT_RENDERER,    currentConfig.renderer32x, renderer_names32x),
757         mee_onoff_h   ("PWM audio",         MA_32XOPT_PWM,         PicoIn.opt, POPT_EN_PWM, h_pwm),
758         mee_onoff_h   ("PWM IRQ optimization", MA_OPT2_PWM_IRQ_OPT, PicoIn.opt, POPT_PWM_IRQ_OPT, h_pwmopt),
759         mee_end,
760 };
761
762 static int menu_loop_32x_options(int id, int keys)
763 {
764         static int sel = 0;
765
766         if (renderer_names32x[0] == NULL)
767                 me_enable(e_menu_32x_options, MA_32XOPT_RENDERER, 0);
768         me_loop_d(e_menu_32x_options, &sel, menu_draw_prep, NULL);
769
770         return 0;
771 }
772
773 #endif
774
775 // ------------ SMS options menu ------------
776
777 #ifndef NO_SMS
778
779 static const char *sms_hardwares[] = { "auto", "Game Gear", "Master System", "SG-1000", "SC-3000", NULL };
780 static const char *gg_ghosting_opts[] = { "OFF", "weak", "normal", NULL };
781 static const char *sms_mappers[] = { "auto", "Sega", "Codemasters", "Korea", "Korea MSX", "Korea X-in-1", "Korea 4-Pak", "Korea Janggun", "Korea Nemesis", "Taiwan 8K RAM", "Korea XOR", "Sega 32K RAM", NULL };
782 static const char *sms_tmspalette[] = { "SMS", "SG-1000", NULL };
783
784 static const char h_smsfm[] = "FM sound is only supported by few games,\n"
785                                 "some games may crash with FM enabled";
786 static const char h_ghost[] = "Simulate the inertia of the GG LCD display";
787 static const char h_smspal[] = "Selects the color palette used for SMS games\n"
788                                 "using the original TMS9918 graphics modes";
789
790 static menu_entry e_menu_sms_options[] =
791 {
792         mee_enum      ("System",            MA_SMSOPT_HARDWARE, PicoIn.hwSelect, sms_hardwares),
793         mee_enum      ("Cartridge mapping", MA_SMSOPT_MAPPER, PicoIn.mapper, sms_mappers),
794         mee_enum_h    ("Game Gear LCD ghosting", MA_SMSOPT_GHOSTING, currentConfig.ghosting, gg_ghosting_opts, h_ghost),
795         mee_onoff_h   ("FM Sound Unit",     MA_OPT2_ENABLE_YM2413, PicoIn.opt, POPT_EN_YM2413, h_smsfm),
796         mee_enum_h    ("SMS palette in TMS mode", MA_SMSOPT_TMSPALETTE, PicoIn.tmsPalette, sms_tmspalette, h_smspal),
797         mee_end,
798 };
799
800 static int menu_loop_sms_options(int id, int keys)
801 {
802         static int sel = 0;
803
804         me_loop_d(e_menu_sms_options, &sel, menu_draw_prep, NULL);
805
806         return 0;
807 }
808
809 #endif
810
811 // ------------ adv options menu ------------
812
813 static const char h_gglcd[] = "Show full VDP image with borders if disabled";
814 static const char h_ovrclk[] = "Will break some games, keep at 0";
815 static const char h_dynarec[] = "Disabling dynarecs massively slows down 32X";
816 static const char h_sh2cycles[]  = "Cycles/millisecond (similar to DOSBox)\n"
817                                    "lower values speed up emulation but break games\n"
818                                    "at least 11000 recommended for compatibility";
819
820 static menu_entry e_menu_adv_options[] =
821 {
822         mee_onoff     ("Disable frame limiter",    MA_OPT2_NO_FRAME_LIMIT,currentConfig.EmuOpt, EOPT_NO_FRMLIMIT),
823         mee_onoff     ("Disable sprite limit",     MA_OPT2_NO_SPRITE_LIM, PicoIn.opt, POPT_DIS_SPRITE_LIM),
824         mee_onoff     ("Disable idle loop patching",MA_OPT2_NO_IDLE_LOOPS,PicoIn.opt, POPT_DIS_IDLE_DET),
825         mee_onoff_h   ("Emulate Game Gear LCD",    MA_OPT2_ENABLE_GGLCD  ,PicoIn.opt, POPT_EN_GG_LCD, h_gglcd),
826         mee_range_h   ("Overclock M68k (%)",       MA_OPT2_OVERCLOCK_M68K,currentConfig.overclock_68k, 0, 1000, h_ovrclk),
827         mee_onoff_h   ("Enable dynarecs",          MA_OPT2_DYNARECS,      PicoIn.opt, POPT_EN_DRC, h_dynarec),
828         mee_cust_h    ("Master SH2 cycles",        MA_32XOPT_MSH2_CYCLES, mh_opt_sh2cycles, mgn_opt_sh2cycles, h_sh2cycles),
829         mee_cust_h    ("Slave SH2 cycles",         MA_32XOPT_SSH2_CYCLES, mh_opt_sh2cycles, mgn_opt_sh2cycles, h_sh2cycles),
830         MENU_OPTIONS_ADV
831         mee_end,
832 };
833
834 static int menu_loop_adv_options(int id, int keys)
835 {
836         static int sel = 0;
837
838         me_loop_d(e_menu_adv_options, &sel, menu_draw_prep, NULL);
839         PicoIn.overclockM68k = currentConfig.overclock_68k; // int vs short
840
841         return 0;
842 }
843
844 // ------------ sound options menu ------------
845
846 static int sndrate_prevnext(int rate, int dir)
847 {
848         const int *rates = plat_target.sound_rates;
849         int rate_count;
850         int i;
851
852         for (rate_count = 0; rates[rate_count] != -1; rate_count++)
853                 ;
854         for (i = 0; i < rate_count; i++)
855                 if (rates[i] == rate) break;
856
857         i += dir ? 1 : -1;
858         if (i >= rate_count) {
859                 if (!(PicoIn.opt & POPT_EN_STEREO)) {
860                         PicoIn.opt |= POPT_EN_STEREO;
861                         return rates[0];
862                 }
863                 return rates[rate_count-1];
864         }
865         if (i < 0) {
866                 if (PicoIn.opt & POPT_EN_STEREO) {
867                         PicoIn.opt &= ~POPT_EN_STEREO;
868                         return rates[rate_count-1];
869                 }
870                 return rates[0];
871         }
872         return rates[i];
873 }
874
875 static int mh_opt_snd(int id, int keys)
876 {
877         PicoIn.sndRate = sndrate_prevnext(PicoIn.sndRate, keys & PBTN_RIGHT);
878         return 0;
879 }
880
881 static const char *mgn_opt_sound(int id, int *offs)
882 {
883         const char *str2;
884         *offs = -8;
885         str2 = (PicoIn.opt & POPT_EN_STEREO) ? "stereo" : "mono";
886         if (PicoIn.sndRate > 52000 && PicoIn.sndRate < 54000)
887                 sprintf(static_buff, "native  %s", str2);
888         else    sprintf(static_buff, "%5iHz %s", PicoIn.sndRate, str2);
889         return static_buff;
890 }
891
892 static int mh_opt_alpha(int id, int keys)
893 {
894         int val = (PicoIn.sndFilterAlpha * 100 + 0x08000) / 0x10000;
895         if (keys & PBTN_LEFT)   val--;
896         if (keys & PBTN_RIGHT)  val++;
897         if (val <  1)           val = 1;
898         if (val > 99)           val = 99;
899         PicoIn.sndFilterAlpha = val * 0x10000 / 100;
900         return 0;
901 }
902
903 static const char *mgn_opt_alpha(int id, int *offs)
904 {
905         int val = (PicoIn.sndFilterAlpha * 100 + 0x08000) / 0x10000;
906         sprintf(static_buff, "0.%02d", val);
907         return static_buff;
908 }
909
910 static const char h_ensound[] = "Disabling turns off sound output, however all\n"
911                                 "enabled sound components are still emulated";
912 static const char h_quality[] = "native: Mega Drive FM hardware rate (~53000Hz),\n"
913                                 "best quality, but may not work on some devices";
914 static const char h_lowpass[] = "Low pass filter for sound closer to real hardware";
915 static const char h_lpalpha[] = "Higher values have more impact";
916
917 static menu_entry e_menu_snd_options[] =
918 {
919         mee_onoff_h   ("Enable sound",    MA_OPT_ENABLE_SOUND,  currentConfig.EmuOpt, EOPT_EN_SOUND, h_ensound),
920         mee_cust_h    ("Sound quality",   MA_OPT_SOUND_QUALITY, mh_opt_snd, mgn_opt_sound, h_quality),
921         mee_onoff_h   ("Sound filter",    MA_OPT_SOUND_FILTER,  PicoIn.opt, POPT_EN_SNDFILTER, h_lowpass),
922         mee_cust_h    ("Filter strength", MA_OPT_SOUND_ALPHA,   mh_opt_alpha, mgn_opt_alpha, h_lpalpha),
923         mee_end,
924 };
925
926 static int menu_loop_snd_options(int id, int keys)
927 {
928         static int sel = 0;
929
930         if (PicoIn.sndRate > 52000 && PicoIn.sndRate < 54000)
931                 PicoIn.sndRate = 53000;
932         me_loop_d(e_menu_snd_options, &sel, menu_draw_prep, NULL);
933
934         return 0;
935 }
936
937 // ------------ gfx options menu ------------
938
939 static const char h_gamma[] = "Gamma/brightness adjustment (default 1.00)";
940
941 static const char *mgn_opt_fskip(int id, int *offs)
942 {
943         if (currentConfig.Frameskip < 0)
944                 return "Auto";
945         sprintf(static_buff, "%d", currentConfig.Frameskip);
946         return static_buff;
947 }
948
949 static const char *mgn_aopt_gamma(int id, int *offs)
950 {
951         sprintf(static_buff, "%i.%02i", currentConfig.gamma / 100, currentConfig.gamma % 100);
952         return static_buff;
953 }
954
955 static menu_entry e_menu_gfx_options[] =
956 {
957         mee_enum      ("Video output mode", MA_OPT_VOUT_MODE, plat_target.vout_method, men_dummy),
958         mee_range_cust("Frameskip",         MA_OPT_FRAMESKIP, currentConfig.Frameskip, -1, 16, mgn_opt_fskip),
959         mee_range     ("Max auto frameskip",MA_OPT2_MAX_FRAMESKIP, currentConfig.max_skip, 1, 10),
960         mee_enum      ("Filter",            MA_OPT3_FILTERING, currentConfig.filter, men_dummy),
961         mee_range_cust_h("Gamma correction",MA_OPT2_GAMMA, currentConfig.gamma, 1, 300, mgn_aopt_gamma, h_gamma),
962         MENU_OPTIONS_GFX
963         mee_end,
964 };
965
966 static int menu_loop_gfx_options(int id, int keys)
967 {
968         static int sel = 0;
969
970         me_loop_d(e_menu_gfx_options, &sel, menu_draw_prep, NULL);
971
972         return 0;
973 }
974
975 // ------------ UI options menu ------------
976
977 static const char *men_confirm_save[] = { "OFF", "writes", "loads", "both", NULL };
978 static const char h_confirm_save[]    = "Ask for confirmation when overwriting save,\n"
979                                         "loading state or both";
980
981 static menu_entry e_menu_ui_options[] =
982 {
983         mee_onoff     ("Show FPS",                 MA_OPT_SHOW_FPS,       currentConfig.EmuOpt, EOPT_SHOW_FPS),
984         mee_enum_h    ("Confirm save/load",        MA_OPT_CONFIRM_STATES, currentConfig.confirm_save, men_confirm_save, h_confirm_save),
985         mee_onoff     ("Don't save last used game", MA_OPT2_NO_LAST_ROM,  currentConfig.EmuOpt, EOPT_NO_AUTOSVCFG),
986         mee_end,
987 };
988
989 static int menu_loop_ui_options(int id, int keys)
990 {
991         static int sel = 0;
992
993         me_loop_d(e_menu_ui_options, &sel, menu_draw_prep, NULL);
994
995         return 0;
996 }
997
998 // ------------ options menu ------------
999
1000 static int find_renderer(const char *names[], const char *which)
1001 {
1002         int i = 0;
1003         for (i = 0; *names; names++, i++)
1004                 if (strstr(*names, which)) return i;
1005         return 0;
1006 }
1007
1008 static int mh_profile(int id, int keys) {
1009         switch (id) {
1010         case MA_PROFILE_ACCURATE:
1011                 currentConfig.renderer = find_renderer(renderer_names, "16bit");
1012                 currentConfig.renderer32x = find_renderer(renderer_names32x, "accurate");
1013                 PicoIn.sndRate = 44100;
1014                 PicoIn.opt |= POPT_EN_FM_FILTER | POPT_EN_FM | POPT_EN_MCD_CDDA;
1015                 PicoIn.opt &= ~POPT_PWM_IRQ_OPT;
1016                 break;
1017         case MA_PROFILE_BALANCED:
1018                 currentConfig.renderer = find_renderer(renderer_names, "8bit");
1019                 currentConfig.renderer32x = find_renderer(renderer_names32x, "fast");
1020                 PicoIn.sndRate = 44100;
1021                 PicoIn.opt |= POPT_EN_FM | POPT_EN_MCD_CDDA;
1022                 PicoIn.opt &= ~(POPT_PWM_IRQ_OPT | POPT_EN_FM_FILTER);
1023                 break;
1024         case MA_PROFILE_FAST:
1025                 currentConfig.renderer = find_renderer(renderer_names, "fast");
1026                 currentConfig.renderer32x = find_renderer(renderer_names32x, "fastest");
1027                 PicoIn.sndRate = 22050;
1028                 PicoIn.opt |= POPT_PWM_IRQ_OPT | POPT_EN_FM | POPT_EN_MCD_CDDA;
1029                 PicoIn.opt &= ~POPT_EN_FM_FILTER;
1030                 break;
1031         case MA_PROFILE_BREAKING:
1032                 currentConfig.renderer = find_renderer(renderer_names, "fast");
1033                 currentConfig.renderer32x = find_renderer(renderer_names32x, "fastest");
1034                 PicoIn.sndRate = 16000;
1035                 PicoIn.opt |= POPT_PWM_IRQ_OPT;
1036                 PicoIn.opt &= ~(POPT_EN_FM_FILTER | POPT_EN_FM | POPT_EN_MCD_CDDA);
1037                 break;
1038         }
1039         return 1;
1040 }
1041
1042 static menu_entry e_menu_profile[] =
1043 {
1044         mee_label     ("Select option profile and press OK:"),
1045         mee_handler_id("accurate", MA_PROFILE_ACCURATE, mh_profile),
1046         mee_handler_id("balanced", MA_PROFILE_BALANCED, mh_profile),
1047         mee_handler_id("fast",     MA_PROFILE_FAST,     mh_profile),
1048         mee_handler_id("breaking", MA_PROFILE_BREAKING, mh_profile),
1049         mee_label     (""),
1050         mee_label     ("Options changed by Option profiles:"),
1051         mee_label     (""),
1052         mee_label     ("Sound: Sound quality"),
1053         mee_label     ("MD:    Renderer, FM audio, FM filter"),
1054         mee_label     ("32X:   Renderer, PWM IRQ optimization"),
1055         mee_label     ("CD:    CDDA audio"),
1056         mee_end,
1057 };
1058
1059 static int menu_loop_profile_options(int id, int keys)
1060 {
1061         static int sel = 0;
1062
1063         me_loop_d(e_menu_profile, &sel, menu_draw_prep, NULL);
1064
1065         return 0;
1066 }
1067
1068 static void region_prevnext(int right)
1069 {
1070         // jp_ntsc=1, jp_pal=2, usa=4, eu=8
1071         static const int rgn_orders[] = { 0x148, 0x184, 0x814, 0x418, 0x841, 0x481 };
1072         int i;
1073
1074         if (right) {
1075                 if (!PicoIn.regionOverride) {
1076                         for (i = 0; i < 6; i++)
1077                                 if (rgn_orders[i] == PicoIn.autoRgnOrder) break;
1078                         if (i < 5) PicoIn.autoRgnOrder = rgn_orders[i+1];
1079                         else PicoIn.regionOverride=1;
1080                 }
1081                 else
1082                         PicoIn.regionOverride <<= 1;
1083                 if (PicoIn.regionOverride > 8)
1084                         PicoIn.regionOverride = 8;
1085         } else {
1086                 if (!PicoIn.regionOverride) {
1087                         for (i = 0; i < 6; i++)
1088                                 if (rgn_orders[i] == PicoIn.autoRgnOrder) break;
1089                         if (i > 0) PicoIn.autoRgnOrder = rgn_orders[i-1];
1090                 }
1091                 else
1092                         PicoIn.regionOverride >>= 1;
1093         }
1094 }
1095
1096 static int mh_opt_misc(int id, int keys)
1097 {
1098         switch (id) {
1099         case MA_OPT_REGION:
1100                 region_prevnext(keys & PBTN_RIGHT);
1101                 break;
1102         default:
1103                 break;
1104         }
1105         return 0;
1106 }
1107
1108 static int mh_restore_defaults(int id, int keys)
1109 {
1110         emu_set_defconfig();
1111         menu_update_msg("defaults restored");
1112         return 1;
1113 }
1114
1115 static const char *mgn_opt_region(int id, int *offs)
1116 {
1117         static const char *names[] = { "Auto", "      Japan NTSC", "      Japan PAL", "      USA", "      Europe" };
1118         static const char *names_short[] = { "", " JP", " JP", " US", " EU" };
1119         int code = PicoIn.regionOverride;
1120         int u, i = 0;
1121
1122         *offs = -6;
1123         if (code) {
1124                 code <<= 1;
1125                 while ((code >>= 1)) i++;
1126                 if (i > 4)
1127                         return "unknown";
1128                 return names[i];
1129         } else {
1130                 strcpy(static_buff, "Auto:");
1131                 for (u = 0; u < 3; u++) {
1132                         code = (PicoIn.autoRgnOrder >> u*4) & 0xf;
1133                         for (i = 0; code; code >>= 1, i++)
1134                                 ;
1135                         strcat(static_buff, names_short[i]);
1136                 }
1137                 return static_buff;
1138         }
1139 }
1140
1141 static const char h_hotkeysvld[] = "Slot used for save/load by emulator hotkey";
1142
1143 static menu_entry e_menu_options[] =
1144 {
1145         mee_cust      ("Region",                   MA_OPT_REGION,        mh_opt_misc, mgn_opt_region),
1146         mee_range     ("",                         MA_OPT_CPU_CLOCKS,    currentConfig.CPUclock, 20, 3200),
1147         mee_range_h   ("Hotkey save/load slot",    MA_OPT_SAVE_SLOT,     state_slot, 0, 9, h_hotkeysvld),
1148         mee_handler   ("Configure controls",       menu_loop_keyconfig),
1149         mee_label     (""),
1150         mee_handler   ("Option profiles",          menu_loop_profile_options),
1151         mee_handler   ("Interface options",        menu_loop_ui_options),
1152         mee_handler   ("Display options",          menu_loop_gfx_options),
1153         mee_handler   ("Sound options",            menu_loop_snd_options),
1154         mee_handler   ("MD/Genesis/Pico options",  menu_loop_md_options),
1155         mee_handler   ("  Sega/Mega CD add-on",    menu_loop_cd_options),
1156 #ifndef NO_32X
1157         mee_handler   ("  32X add-on",             menu_loop_32x_options),
1158 #endif
1159 #ifndef NO_SMS
1160         mee_handler   ("SG/SMS/GG options",        menu_loop_sms_options),
1161 #endif
1162         mee_handler   ("Advanced options",         menu_loop_adv_options),
1163
1164         mee_handler   ("Restore defaults",         mh_restore_defaults),
1165         mee_end,
1166 };
1167
1168 static int menu_loop_options(int id, int keys)
1169 {
1170         static int sel = 0;
1171
1172         me_loop_d(e_menu_options, &sel, menu_draw_prep, NULL);
1173
1174         return 0;
1175 }
1176
1177 // ------------ debug menu ------------
1178
1179 #include <pico/debug.h>
1180
1181 extern void SekStepM68k(void);
1182
1183 static void mplayer_loop(void)
1184 {
1185         pemu_sound_start();
1186
1187         while (1)
1188         {
1189                 PDebugZ80Frame();
1190                 if (in_menu_wait_any(NULL, 0) & PBTN_MA3)
1191                         break;
1192                 emu_sound_wait();
1193         }
1194
1195         emu_sound_stop();
1196 }
1197
1198 static void draw_text_debug(const char *str, int skip, int from)
1199 {
1200         const char *p;
1201         int line;
1202
1203         p = str;
1204         while (skip-- > 0)
1205         {
1206                 while (*p && *p != '\n')
1207                         p++;
1208                 if (*p == 0 || p[1] == 0)
1209                         return;
1210                 p++;
1211         }
1212
1213         str = p;
1214         for (line = from; line < g_menuscreen_h / me_sfont_h; line++)
1215         {
1216                 smalltext_out16(1, line * me_sfont_h, str, PXMAKE(0xff, 0xff, 0xff));
1217                 while (*p && *p != '\n')
1218                         p++;
1219                 if (*p == 0)
1220                         break;
1221                 p++; str = p;
1222         }
1223 }
1224
1225 #ifdef __GNUC__
1226 #define COMPILER "gcc " __VERSION__
1227 #else
1228 #define COMPILER
1229 #endif
1230
1231 static void draw_frame_debug(void)
1232 {
1233         char layer_str[48] = "layers:                   ";
1234         struct PicoVideo *pv = &Pico.video;
1235
1236         if (!(pv->debug_p & PVD_KILL_B))    memcpy(layer_str +  8, "B", 1);
1237         if (!(pv->debug_p & PVD_KILL_A))    memcpy(layer_str + 10, "A", 1);
1238         if (!(pv->debug_p & PVD_KILL_S_LO)) memcpy(layer_str + 12, "spr_lo", 6);
1239         if (!(pv->debug_p & PVD_KILL_S_HI)) memcpy(layer_str + 19, "spr_hi", 6);
1240         if (!(pv->debug_p & PVD_KILL_32X))  memcpy(layer_str + 26, "32x", 4);
1241
1242         pemu_forced_frame(1, 0);
1243         make_bg(1, 1);
1244
1245         smalltext_out16(4, 1, "build: r" REVISION "  "__DATE__ " " __TIME__ " " COMPILER, PXMAKE(0xff, 0xff, 0xff));
1246         smalltext_out16(4, g_menuscreen_h - me_sfont_h, layer_str, PXMAKE(0xff, 0xff, 0xff));
1247 }
1248
1249 static void debug_menu_loop(void)
1250 {
1251         struct PicoVideo *pv = &Pico.video;
1252         int inp, mode = 0;
1253         int spr_offs = 0, dumped = 0;
1254         char *tmp;
1255
1256         while (1)
1257         {
1258                 menu_draw_begin(1, 0);
1259                 g_screen_ptr = g_menuscreen_ptr;
1260                 g_screen_width = g_menuscreen_w;
1261                 g_screen_height = g_menuscreen_h;
1262                 g_screen_ppitch = g_menuscreen_pp;
1263                 switch (mode)
1264                 {
1265                         case 0: tmp = PDebugMain();
1266                                 plat_debug_cat(tmp);
1267                                 draw_text_debug(tmp, 0, 0);
1268                                 if (dumped) {
1269                                         smalltext_out16(g_menuscreen_w - 6 * me_sfont_h,
1270                                                 g_menuscreen_h - me_mfont_h, "dumped", PXMAKE(0xff, 0xff, 0xff));
1271                                         dumped = 0;
1272                                 }
1273                                 break;
1274                         case 1: draw_frame_debug();
1275                                 break;
1276                         case 2: pemu_forced_frame(1, 0);
1277                                 make_bg(1, 1);
1278                                 PDebugShowSpriteStats((unsigned short *)g_menuscreen_ptr
1279                                         + (g_menuscreen_h/2 - 240/2) * g_menuscreen_pp
1280                                         + g_menuscreen_w/2 - 320/2, g_menuscreen_pp);
1281                                 break;
1282                         case 3: menuscreen_memset_lines(g_menuscreen_ptr, 0, g_menuscreen_h);
1283                                 PDebugShowPalette(g_menuscreen_ptr, g_menuscreen_pp);
1284                                 PDebugShowSprite((unsigned short *)g_menuscreen_ptr
1285                                         + g_menuscreen_pp * 120 + g_menuscreen_w / 2 + 16,
1286                                         g_menuscreen_pp, spr_offs);
1287                                 draw_text_debug(PDebugSpriteList(), spr_offs, 6);
1288                                 break;
1289                         case 4: tmp = PDebug32x();
1290                                 draw_text_debug(tmp, 0, 0);
1291                                 break;
1292                 }
1293                 menu_draw_end();
1294
1295                 inp = in_menu_wait(PBTN_MOK|PBTN_MBACK|PBTN_MA2|PBTN_MA3|PBTN_L|PBTN_R |
1296                                         PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT, NULL, 70);
1297                 if (inp & PBTN_MBACK) return;
1298                 if (inp & PBTN_L) { mode--; if (mode < 0) mode = 4; }
1299                 if (inp & PBTN_R) { mode++; if (mode > 4) mode = 0; }
1300                 switch (mode)
1301                 {
1302                         case 0:
1303                                 if (inp & PBTN_MOK)
1304                                         PDebugCPUStep();
1305                                 if (inp & PBTN_MA3) {
1306                                         while (inp & PBTN_MA3)
1307                                                 inp = in_menu_wait_any(NULL, -1);
1308                                         mplayer_loop();
1309                                 }
1310                                 if ((inp & (PBTN_MA2|PBTN_LEFT)) == (PBTN_MA2|PBTN_LEFT)) {
1311                                         mkdir("dumps", 0777);
1312                                         PDebugDumpMem();
1313                                         while (inp & PBTN_MA2) inp = in_menu_wait_any(NULL, -1);
1314                                         dumped = 1;
1315                                 }
1316                                 break;
1317                         case 1:
1318                                 if (inp & PBTN_LEFT)  pv->debug_p ^= PVD_KILL_B;
1319                                 if (inp & PBTN_RIGHT) pv->debug_p ^= PVD_KILL_A;
1320                                 if (inp & PBTN_DOWN)  pv->debug_p ^= PVD_KILL_S_LO;
1321                                 if (inp & PBTN_UP)    pv->debug_p ^= PVD_KILL_S_HI;
1322                                 if (inp & PBTN_MA2)   pv->debug_p ^= PVD_KILL_32X;
1323                                 if (inp & PBTN_MOK) {
1324                                         PicoIn.sndOut = NULL; // just in case
1325                                         PicoIn.skipFrame = 1;
1326                                         PicoFrame();
1327                                         PicoIn.skipFrame = 0;
1328                                         while (inp & PBTN_MOK) inp = in_menu_wait_any(NULL, -1);
1329                                 }
1330                                 break;
1331                         case 3:
1332                                 if (inp & PBTN_DOWN)  spr_offs++;
1333                                 if (inp & PBTN_UP)    spr_offs--;
1334                                 if (spr_offs < 0) spr_offs = 0;
1335                                 break;
1336                 }
1337         }
1338 }
1339
1340 // ------------ main menu ------------
1341
1342 static void draw_frame_credits(void)
1343 {
1344         smalltext_out16(4, 1, "build: " __DATE__ " " __TIME__, PXMAKE(0xe0, 0xff, 0xe0));
1345 }
1346
1347 static const char credits[] =
1348         "PicoDrive v" VERSION "\n"
1349         "(c) notaz, 2006-2013; irixxxx, 2018-2024\n\n"
1350         "Credits:\n"
1351         "fDave: initial code\n"
1352 #ifdef EMU_C68K
1353         "      Cyclone 68000 core\n"
1354 #else
1355         "Stef, Chui: FAME/C 68k core\n"
1356 #endif
1357 #ifdef _USE_DRZ80
1358         "Reesy & FluBBa: DrZ80 core\n"
1359 #else
1360         "Stef, NJ: CZ80 core\n"
1361 #endif
1362         "MAME devs: SH2, YM2612 and SN76496 cores\n"
1363         "Eke, Stef: some Sega CD code\n"
1364         "Inder, ketchupgun: graphics\n"
1365 #ifdef __GP2X__
1366         "Squidge: mmuhack\n"
1367         "Dzz: ARM940 sample\n"
1368 #endif
1369         "\n"
1370         "special thanks (for docs, ideas):\n"
1371         " Charles MacDonald, Haze,\n"
1372         " Stephane Dallongeville,\n"
1373         " Lordus, Exophase, Rokas,\n"
1374         " Eke, Nemesis, Tasco Deluxe";
1375
1376 static void menu_main_draw_status(void)
1377 {
1378         static time_t last_bat_read = 0;
1379         static int last_bat_val = -1;
1380         unsigned short *bp = g_menuscreen_ptr;
1381         int bat_h = me_mfont_h * 2 / 3;
1382         int i, u, w, wfill, batt_val;
1383         struct tm *tmp;
1384         time_t ltime;
1385         char time_s[16];
1386
1387         if (!(currentConfig.EmuOpt & EOPT_SHOW_RTC))
1388                 return;
1389
1390         ltime = time(NULL);
1391         tmp = gmtime(&ltime);
1392         strftime(time_s, sizeof(time_s), "%H:%M", tmp);
1393
1394         text_out16(g_menuscreen_w - me_mfont_w * 6, me_mfont_h + 2, time_s);
1395
1396         if (ltime - last_bat_read > 10) {
1397                 last_bat_read = ltime;
1398                 last_bat_val = batt_val = plat_target_bat_capacity_get();
1399         }
1400         else
1401                 batt_val = last_bat_val;
1402
1403         if (batt_val < 0 || batt_val > 100)
1404                 return;
1405
1406         /* battery info */
1407         bp += (me_mfont_h * 2 + 2) * g_menuscreen_pp + g_menuscreen_w - me_mfont_w * 3 - 3;
1408         for (i = 0; i < me_mfont_w * 2; i++)
1409                 bp[i] = menu_text_color;
1410         for (i = 0; i < me_mfont_w * 2; i++)
1411                 bp[i + g_menuscreen_pp * bat_h] = menu_text_color;
1412         for (i = 0; i <= bat_h; i++)
1413                 bp[i * g_menuscreen_pp] =
1414                 bp[i * g_menuscreen_pp + me_mfont_w * 2] = menu_text_color;
1415         for (i = 2; i < bat_h - 1; i++)
1416                 bp[i * g_menuscreen_pp - 1] =
1417                 bp[i * g_menuscreen_pp - 2] = menu_text_color;
1418
1419         w = me_mfont_w * 2 - 1;
1420         wfill = batt_val * w / 100;
1421         for (u = 1; u < bat_h; u++)
1422                 for (i = 0; i < wfill; i++)
1423                         bp[(w - i) + g_menuscreen_pp * u] = menu_text_color;
1424 }
1425
1426 static menu_entry e_menu_main[];
1427
1428 static int tape_record_bit;
1429 static const char *tape_record_exts[] = { ".wav", ".bit" };
1430
1431 static const char *mgn_loadtape(int id, int *offs)
1432 {
1433         return "";
1434 }
1435
1436 static int mh_loadtape(int id, int keys)
1437 {
1438         if (keys & (PBTN_LEFT|PBTN_RIGHT)) { // multi choice
1439                 int x = me_id2offset(e_menu_main, MA_MAIN_SAVE_TAPE);
1440                 e_menu_main[x].enabled = !e_menu_main[x].enabled;
1441                 return 0;
1442         }
1443         if (keys & PBTN_MOK) {
1444                 static const char *rom_exts[] = { "bit", "wav", NULL };
1445                 const char *ret_name;
1446
1447                 ret_name = menu_loop_romsel_d(rom_fname_loaded,
1448                                 sizeof(rom_fname_loaded), rom_exts, NULL, menu_draw_prep);
1449                 if (ret_name == NULL)
1450                         return 0;
1451
1452                 return emu_play_tape(ret_name);
1453         }
1454         return 1;
1455 }
1456
1457 static const char *mgn_savetape(int id, int *offs)
1458 {
1459         return tape_record_exts[!!tape_record_bit];
1460 }
1461
1462 static int mh_savetape(int id, int keys)
1463 {
1464         if (keys & (PBTN_LEFT|PBTN_RIGHT)) { // multi choice
1465                 tape_record_bit = !tape_record_bit;
1466                 return 0;
1467         }
1468         if (keys & PBTN_MOK) {
1469                 return emu_record_tape(tape_record_exts[!!tape_record_bit]);
1470         }
1471         return 1;
1472 }
1473
1474 static int main_menu_handler(int id, int keys)
1475 {
1476         const char *ret_name;
1477
1478         switch (id)
1479         {
1480         case MA_MAIN_RESUME_GAME:
1481                 if (PicoGameLoaded)
1482                         return 1;
1483                 break;
1484         case MA_MAIN_SAVE_STATE:
1485                 if (PicoGameLoaded)
1486                         return menu_loop_savestate(0);
1487                 break;
1488         case MA_MAIN_LOAD_STATE:
1489                 if (PicoGameLoaded)
1490                         return menu_loop_savestate(1);
1491                 break;
1492         case MA_MAIN_RESET_GAME:
1493                 if (PicoGameLoaded) {
1494                         emu_reset_game();
1495                         return 1;
1496                 }
1497                 break;
1498         case MA_MAIN_LOAD_ROM:
1499                 rom_fname_reload = NULL;
1500                 ret_name = menu_loop_romsel_d(rom_fname_loaded,
1501                         sizeof(rom_fname_loaded), rom_exts, NULL, menu_draw_prep);
1502                 if (ret_name != NULL) {
1503                         lprintf("selected file: %s\n", ret_name);
1504                         rom_fname_reload = ret_name;
1505                         engineState = PGS_ReloadRom;
1506                         return 1;
1507                 }
1508                 break;
1509         case MA_MAIN_CHANGE_CD:
1510                 if (PicoIn.AHW & PAHW_MCD) {
1511                         // if cd is loaded, cdd_unload() triggers eject and
1512                         // returns 1, else we'll select and load new CD here
1513                         if (!cdd_unload())
1514                                 menu_loop_tray();
1515                         return 1;
1516                 }
1517                 break;
1518         case MA_MAIN_CREDITS:
1519                 draw_menu_message(credits, draw_frame_credits);
1520                 in_menu_wait(PBTN_MOK|PBTN_MBACK, NULL, 70);
1521                 break;
1522         case MA_MAIN_EXIT:
1523                 engineState = PGS_Quit;
1524                 return 1;
1525         case MA_MAIN_PATCHES:
1526                 if (PicoGameLoaded && PicoPatches) {
1527                         menu_loop_patches();
1528                         PicoPatchApply();
1529                         menu_update_msg("Patches applied");
1530                 }
1531                 break;
1532         default:
1533                 lprintf("%s: something unknown selected\n", __FUNCTION__);
1534                 break;
1535         }
1536
1537         return 0;
1538 }
1539
1540 static const char *mgn_picopage(int id, int *offs)
1541 {
1542         if (PicoPicohw.page != 7)
1543                 sprintf(static_buff, "%i", PicoPicohw.page);
1544         else    sprintf(static_buff, "Test");
1545         return static_buff;
1546 }
1547
1548 static int mh_picopage(int id, int keys)
1549 {
1550         if (keys & (PBTN_LEFT|PBTN_RIGHT)) { // multi choice
1551                 PicoPicohw.page += (keys & PBTN_LEFT) ? -1 : 1;
1552                 if (PicoPicohw.page < 0) PicoPicohw.page = 7;
1553                 else if (PicoPicohw.page > 7) PicoPicohw.page = 0;
1554                 return 0;
1555         }
1556         return 1;
1557 }
1558
1559 static const char *mgn_saveloadcfg(int id, int *offs)
1560 {
1561         strcpy(static_buff, "   ");
1562         if (config_slot != 0)
1563                 sprintf(static_buff, "[%i]", config_slot);
1564         return static_buff;
1565 }
1566
1567 static int mh_saveloadcfg(int id, int keys)
1568 {
1569         int ret;
1570
1571         if (keys & (PBTN_LEFT|PBTN_RIGHT)) { // multi choice
1572                 config_slot += (keys & PBTN_LEFT) ? -1 : 1;
1573                 if (config_slot < 0) config_slot = 9;
1574                 else if (config_slot > 9) config_slot = 0;
1575                 me_enable(e_menu_main, MA_OPT_LOADCFG, PicoGameLoaded && config_slot != config_slot_current);
1576                 return 0;
1577         }
1578
1579         switch (id) {
1580         case MA_OPT_SAVECFG:
1581         case MA_OPT_SAVECFG_GAME:
1582                 if (emu_write_config(id == MA_OPT_SAVECFG_GAME ? 1 : 0))
1583                         menu_update_msg("config saved");
1584                 else
1585                         menu_update_msg("failed to write config");
1586                 break;
1587         case MA_OPT_LOADCFG:
1588                 ret = emu_read_config(rom_fname_loaded, 1);
1589                 if (!ret) ret = emu_read_config(NULL, 1);
1590                 if (ret)  menu_update_msg("config loaded");
1591                 else      menu_update_msg("failed to load config");
1592                 break;
1593         default:
1594                 return 0;
1595         }
1596
1597         return 1;
1598 }
1599
1600
1601 static const char h_saveload[] = "Game options are overloading global options";
1602
1603 static menu_entry e_menu_main[] =
1604 {
1605         mee_label     ("PicoDrive " VERSION),
1606         mee_label     (""),
1607         mee_label     (""),
1608         mee_handler_id("Resume game",        MA_MAIN_RESUME_GAME, main_menu_handler),
1609         mee_handler_id("Save state",         MA_MAIN_SAVE_STATE,  main_menu_handler),
1610         mee_handler_id("Load state",         MA_MAIN_LOAD_STATE,  main_menu_handler),
1611         mee_handler_id("Reset game",         MA_MAIN_RESET_GAME,  main_menu_handler),
1612         mee_handler_id("Change CD",          MA_MAIN_CHANGE_CD,   main_menu_handler),
1613         mee_cust_s_h  ("Storyware page",     MA_MAIN_PICO_PAGE, 0,mh_picopage, mgn_picopage, NULL),
1614         mee_cust_s_h  ("Load tape",          MA_MAIN_LOAD_TAPE, 0,mh_loadtape, mgn_loadtape, NULL),
1615         mee_cust_s_h  ("Save tape",          MA_MAIN_SAVE_TAPE, 0,mh_savetape, mgn_savetape, NULL),
1616         mee_handler_id("Patches / GameGenie",MA_MAIN_PATCHES,     main_menu_handler),
1617         mee_handler_id("Load new game",      MA_MAIN_LOAD_ROM,    main_menu_handler),
1618         mee_handler   ("Change options",                          menu_loop_options),
1619         mee_cust_s_h  ("Save global options",MA_OPT_SAVECFG, 0,   mh_saveloadcfg, mgn_saveloadcfg, NULL),
1620         mee_cust_s_h  ("Save game options",  MA_OPT_SAVECFG_GAME, 0, mh_saveloadcfg, mgn_saveloadcfg, h_saveload),
1621         mee_cust_s_h  ("Load game options",  MA_OPT_LOADCFG, 0,   mh_saveloadcfg, mgn_saveloadcfg, h_saveload),
1622         mee_handler_id("Credits",            MA_MAIN_CREDITS,     main_menu_handler),
1623         mee_handler_id("Exit",               MA_MAIN_EXIT,        main_menu_handler),
1624         mee_end,
1625 };
1626
1627 void menu_loop(void)
1628 {
1629         static int sel = 0;
1630
1631         me_enable(e_menu_main, MA_MAIN_RESUME_GAME, PicoGameLoaded);
1632         me_enable(e_menu_main, MA_MAIN_SAVE_STATE,  PicoGameLoaded);
1633         me_enable(e_menu_main, MA_MAIN_LOAD_STATE,  PicoGameLoaded);
1634         me_enable(e_menu_main, MA_MAIN_RESET_GAME,  PicoGameLoaded);
1635         me_enable(e_menu_main, MA_MAIN_CHANGE_CD,   PicoIn.AHW & PAHW_MCD);
1636         me_enable(e_menu_main, MA_MAIN_LOAD_TAPE,   PicoIn.AHW & PAHW_SC);
1637         me_enable(e_menu_main, MA_MAIN_SAVE_TAPE,   0);
1638         me_enable(e_menu_main, MA_MAIN_PICO_PAGE,   PicoIn.AHW & PAHW_PICO);
1639         me_enable(e_menu_main, MA_MAIN_PATCHES,     PicoPatches != NULL);
1640         me_enable(e_menu_main, MA_OPT_SAVECFG_GAME, PicoGameLoaded);
1641         me_enable(e_menu_main, MA_OPT_LOADCFG,      PicoGameLoaded && config_slot != config_slot_current);
1642
1643         menu_enter(PicoGameLoaded);
1644         in_set_config_int(0, IN_CFG_BLOCKING, 1);
1645         me_loop_d(e_menu_main, &sel, menu_draw_prep, menu_main_draw_status);
1646
1647         if (PicoGameLoaded) {
1648                 if (engineState == PGS_Menu)
1649                         engineState = PGS_Running;
1650                 /* wait until menu, ok, back is released */
1651                 while (in_menu_wait_any(NULL, 50) & (PBTN_MENU|PBTN_MOK|PBTN_MBACK))
1652                         ;
1653         }
1654
1655         in_set_config_int(0, IN_CFG_BLOCKING, 0);
1656         plat_video_menu_leave();
1657 }
1658
1659 // --------- CD tray close menu ----------
1660
1661 static int mh_tray_load_cd(int id, int keys)
1662 {
1663         const char *ret_name;
1664
1665         rom_fname_reload = NULL;
1666         ret_name = menu_loop_romsel_d(rom_fname_loaded,
1667                         sizeof(rom_fname_loaded), rom_exts, NULL, menu_draw_prep);
1668         if (ret_name == NULL)
1669                 return 0;
1670
1671         rom_fname_reload = ret_name;
1672         engineState = PGS_RestartRun;
1673         return emu_swap_cd(ret_name);
1674 }
1675
1676 static int mh_tray_nothing(int id, int keys)
1677 {
1678         return 1;
1679 }
1680
1681 static menu_entry e_menu_tray[] =
1682 {
1683         mee_label  ("The CD tray has opened."),
1684         mee_label  (""),
1685         mee_label  (""),
1686         mee_handler("Load CD image",  mh_tray_load_cd),
1687         mee_handler("Insert nothing", mh_tray_nothing),
1688         mee_end,
1689 };
1690
1691 int menu_loop_tray(void)
1692 {
1693         int ret = 1, sel = 0;
1694
1695         menu_enter(PicoGameLoaded);
1696
1697         in_set_config_int(0, IN_CFG_BLOCKING, 1);
1698         me_loop_d(e_menu_tray, &sel, menu_draw_prep, NULL);
1699
1700         if (engineState != PGS_RestartRun) {
1701                 engineState = PGS_RestartRun;
1702                 ret = 0; /* no CD inserted */
1703         }
1704
1705         while (in_menu_wait_any(NULL, 50) & (PBTN_MENU|PBTN_MOK|PBTN_MBACK))
1706                 ;
1707         in_set_config_int(0, IN_CFG_BLOCKING, 0);
1708         plat_video_menu_leave();
1709
1710         return ret;
1711 }
1712
1713 void menu_update_msg(const char *msg)
1714 {
1715         strncpy(menu_error_msg, msg, sizeof(menu_error_msg));
1716         menu_error_msg[sizeof(menu_error_msg) - 1] = 0;
1717
1718         menu_error_time = plat_get_ticks_ms();
1719         lprintf("msg: %s\n", menu_error_msg);
1720 }
1721
1722 // ------------ util ------------
1723
1724 /* hidden options for config engine only */
1725 static menu_entry e_menu_hidden[] =
1726 {
1727         mee_onoff("Accurate sprites",         MA_OPT_ACC_SPRITES,    PicoIn.opt, POPT_ACC_SPRITES),
1728 //      mee_range("Save slot",                MA_OPT_SAVE_SLOT,      state_slot, 0, 9),
1729
1730 //      mee_enum ("Confirm savestate",        MA_OPT_CONFIRM_STATES, currentConfig.confirm_save, men_confirm_save),
1731         mee_onoff("autoload savestates",      MA_OPT_AUTOLOAD_SAVE,  g_autostateld_opt, 1),
1732         mee_onoff("SDL fullscreen mode",      MA_OPT_VOUT_FULL,      plat_target.vout_fullscreen, 1),
1733         mee_onoff("Emulate Z80",              MA_OPT2_ENABLE_Z80,    PicoIn.opt, POPT_EN_Z80),
1734         mee_onoff("Emulate YM2612 (FM)",      MA_OPT2_ENABLE_YM2612, PicoIn.opt, POPT_EN_FM),
1735         mee_onoff("Disable YM2612 SSG-EG",    MA_OPT2_DISABLE_YM_SSG,PicoIn.opt, POPT_DIS_FM_SSGEG),
1736         mee_onoff("Enable YM2612 DAC noise",  MA_OPT2_ENABLE_YM_DAC, PicoIn.opt, POPT_EN_FM_DAC),
1737         mee_onoff("Emulate SN76496 (PSG)",    MA_OPT2_ENABLE_SN76496,PicoIn.opt, POPT_EN_PSG),
1738         mee_onoff("Scale/Rot. fx",            MA_CDOPT_SCALEROT_CHIP,PicoIn.opt, POPT_EN_MCD_GFX),
1739         mee_onoff("32X enabled",              MA_32XOPT_ENABLE_32X,  PicoIn.opt, POPT_EN_32X),
1740         mee_end,
1741 };
1742
1743 static menu_entry *e_menu_table[] =
1744 {
1745         e_menu_options,
1746         e_menu_ui_options,
1747         e_menu_snd_options,
1748         e_menu_gfx_options,
1749         e_menu_adv_options,
1750         e_menu_md_options,
1751         e_menu_cd_options,
1752 #ifndef NO_32X
1753         e_menu_32x_options,
1754 #endif
1755 #ifndef NO_SMS
1756         e_menu_sms_options,
1757 #endif
1758         e_menu_keyconfig,
1759         e_menu_hidden,
1760 };
1761
1762 static menu_entry *me_list_table = NULL;
1763 static menu_entry *me_list_i = NULL;
1764
1765 menu_entry *me_list_get_first(void)
1766 {
1767         me_list_table = me_list_i = e_menu_table[0];
1768         return me_list_i;
1769 }
1770
1771 menu_entry *me_list_get_next(void)
1772 {
1773         int i;
1774
1775         me_list_i++;
1776         if (me_list_i->name != NULL)
1777                 return me_list_i;
1778
1779         for (i = 0; i < array_size(e_menu_table); i++)
1780                 if (me_list_table == e_menu_table[i])
1781                         break;
1782
1783         if (i + 1 < array_size(e_menu_table))
1784                 me_list_table = me_list_i = e_menu_table[i + 1];
1785         else
1786                 me_list_table = me_list_i = NULL;
1787
1788         return me_list_i;
1789 }
1790
1791 void menu_init(void)
1792 {
1793         int i;
1794
1795         menu_init_base();
1796
1797         i = 0;
1798 #if defined(_SVP_DRC) || defined(DRC_SH2)
1799         i = 1;
1800 #endif
1801         me_enable(e_menu_adv_options, MA_OPT2_DYNARECS, i);
1802
1803         i = me_id2offset(e_menu_gfx_options, MA_OPT_VOUT_MODE);
1804         e_menu_gfx_options[i].data = plat_target.vout_methods;
1805         me_enable(e_menu_gfx_options, MA_OPT_VOUT_MODE,
1806                 plat_target.vout_methods != NULL);
1807
1808         i = me_id2offset(e_menu_gfx_options, MA_OPT3_FILTERING);
1809         e_menu_gfx_options[i].data = plat_target.hwfilters;
1810         me_enable(e_menu_gfx_options, MA_OPT3_FILTERING,
1811                 plat_target.hwfilters != NULL);
1812
1813         me_enable(e_menu_gfx_options, MA_OPT2_GAMMA,
1814                 plat_target.gamma_set != NULL);
1815
1816         i = me_id2offset(e_menu_options, MA_OPT_CPU_CLOCKS);
1817         e_menu_options[i].enabled = 0;
1818         if (plat_target.cpu_clock_set != NULL) {
1819                 e_menu_options[i].name = "CPU clock";
1820                 e_menu_options[i].enabled = 1;
1821         }
1822         // suppress warnings about unused libpicofe funcs
1823         (void)me_loop;
1824         (void)menu_loop_romsel;
1825 }