partially working menu
[fceu.git] / drivers / gp2x / menu.c
1 // (c) Copyright 2006,2007 notaz, All rights reserved.\r
2 // Free for non-commercial use.\r
3 \r
4 // For commercial use, separate licencing terms must be obtained.\r
5 \r
6 #include <stdio.h>\r
7 #include <string.h>\r
8 #include <stdlib.h>\r
9 #include <stdarg.h>\r
10 #include <unistd.h>\r
11 #include <dirent.h>\r
12 \r
13 #include "minimal.h"\r
14 #include "usbjoy.h"\r
15 #include "asmutils.h"\r
16 #include "menu.h"\r
17 #include "main.h"\r
18 #include "fonts.h"\r
19 #include "gp2x.h"\r
20 \r
21 #ifndef _DIRENT_HAVE_D_TYPE\r
22 #error "need d_type for file browser\r
23 #endif\r
24 \r
25 extern char lastLoadedGameName[PATH_MAX];\r
26 extern int  mmuhack_status;\r
27 //extern int  state_slot; // TODO\r
28 extern uint8 Exit; // exit emu loop flag\r
29 \r
30 static char *gp2xKeyNames[] = {\r
31         "UP",    "01???",  "LEFT", "03???", "DOWN", "05???", "RIGHT",    "07???",\r
32         "START", "SELECT", "L",    "R",     "A",    "B",     "X",        "Y",\r
33         "10???", "11???",  "12???","13???", "14???","15???", "VOL DOWN", "VOL UP",\r
34         "18???", "19???",  "1a???","PUSH",  "1c???","1d???", "1e???",    "1f???"\r
35 };\r
36 \r
37 char menuErrorMsg[40] = {0, };\r
38 \r
39 // TODO\r
40 void gp2x_fceu_copy_bg(void)\r
41 {\r
42         memset(gp2x_screen, 0, 320*240*2);\r
43 }\r
44 \r
45 // draws white text to current bbp15 screen\r
46 static void gp2x_text_out15_(int x, int y, const char *text)\r
47 {\r
48         int i,l;\r
49         unsigned short *screen = gp2x_screen;\r
50 \r
51         screen = screen + x + y*320;\r
52 \r
53         for (i = 0; i < strlen(text); i++)\r
54         {\r
55                 for (l=0;l<8;l++)\r
56                 {\r
57                         if(fontdata8x8[((text[i])*8)+l]&0x80) screen[l*320+0]=0xffff;\r
58                         if(fontdata8x8[((text[i])*8)+l]&0x40) screen[l*320+1]=0xffff;\r
59                         if(fontdata8x8[((text[i])*8)+l]&0x20) screen[l*320+2]=0xffff;\r
60                         if(fontdata8x8[((text[i])*8)+l]&0x10) screen[l*320+3]=0xffff;\r
61                         if(fontdata8x8[((text[i])*8)+l]&0x08) screen[l*320+4]=0xffff;\r
62                         if(fontdata8x8[((text[i])*8)+l]&0x04) screen[l*320+5]=0xffff;\r
63                         if(fontdata8x8[((text[i])*8)+l]&0x02) screen[l*320+6]=0xffff;\r
64                         if(fontdata8x8[((text[i])*8)+l]&0x01) screen[l*320+7]=0xffff;\r
65                 }\r
66                 screen += 8;\r
67         }\r
68 }\r
69 \r
70 void gp2x_text_out15(int x, int y, const char *texto, ...)\r
71 {\r
72         va_list args;\r
73         char    buffer[512];\r
74 \r
75         va_start(args,texto);\r
76         vsprintf(buffer,texto,args);\r
77         va_end(args);\r
78 \r
79         gp2x_text_out15_(x,y,buffer);\r
80 }\r
81 \r
82 \r
83 void gp2x_text_out15_lim(int x, int y, const char *texto, int max)\r
84 {\r
85         char    buffer[320/8+1];\r
86 \r
87         strncpy(buffer, texto, 320/8);\r
88         if (max > 320/8) max = 320/8;\r
89         if (max < 0) max = 0;\r
90         buffer[max] = 0;\r
91 \r
92         gp2x_text_out15(x,y,buffer);\r
93 }\r
94 \r
95 static void gp2x_smalltext16(int x, int y, const char *texto)\r
96 {\r
97         int i;\r
98         unsigned char  *src;\r
99         unsigned short *dst;\r
100 \r
101         for (i = 0;; i++, x += 6)\r
102         {\r
103                 unsigned char c = (unsigned char) texto[i];\r
104                 int h = 8;\r
105 \r
106                 if (!c) break;\r
107 \r
108                 src = fontdata6x8[c];\r
109                 dst = (unsigned short *)gp2x_screen + x + y*320;\r
110 \r
111                 while (h--)\r
112                 {\r
113                         int w = 0x20;\r
114                         while (w)\r
115                         {\r
116                                 if( *src & w ) *dst = 0xffff;\r
117                                 dst++;\r
118                                 w>>=1;\r
119                         }\r
120                         src++;\r
121 \r
122                         dst += 320-6;\r
123                 }\r
124         }\r
125 }\r
126 \r
127 static void gp2x_smalltext8_lim(int x, int y, const char *texto, int max)\r
128 {\r
129         char    buffer[320/6+1];\r
130 \r
131         strncpy(buffer, texto, 320/6);\r
132         if (max > 320/6) max = 320/6;\r
133         if (max < 0) max = 0;\r
134         buffer[max] = 0;\r
135 \r
136         gp2x_smalltext16(x, y, buffer);\r
137 }\r
138 \r
139 \r
140 static unsigned long inp_prev = 0;\r
141 static int inp_prevjoy = 0;\r
142 \r
143 static unsigned long wait_for_input(unsigned long interesting)\r
144 {\r
145         unsigned long ret;\r
146         static int repeats = 0, wait = 50*1000;\r
147         int release = 0, i;\r
148 \r
149         if (repeats == 2 || repeats == 4) wait /= 2;\r
150         if (repeats == 6) wait = 15 * 1000;\r
151 \r
152         for (i = 0; i < 6 && inp_prev == gp2x_joystick_read(1); i++) {\r
153                 if (i == 0) repeats++;\r
154                 if (wait >= 30*1000) usleep(wait); // usleep sleeps for ~30ms minimum\r
155                 else spend_cycles(wait * Settings.cpuclock);\r
156         }\r
157 \r
158         while ( !((ret = gp2x_joystick_read(1)) & interesting) ) {\r
159                 usleep(50000);\r
160                 release = 1;\r
161         }\r
162 \r
163         if (release || ret != inp_prev) {\r
164                 repeats = 0;\r
165                 wait = 50*1000;\r
166         }\r
167         inp_prev = ret;\r
168         inp_prevjoy = 0;\r
169 \r
170         // we don't need diagonals in menus\r
171         if ((ret&GP2X_UP)   && (ret&GP2X_LEFT))  ret &= ~GP2X_LEFT;\r
172         if ((ret&GP2X_UP)   && (ret&GP2X_RIGHT)) ret &= ~GP2X_RIGHT;\r
173         if ((ret&GP2X_DOWN) && (ret&GP2X_LEFT))  ret &= ~GP2X_LEFT;\r
174         if ((ret&GP2X_DOWN) && (ret&GP2X_RIGHT)) ret &= ~GP2X_RIGHT;\r
175 \r
176         return ret;\r
177 }\r
178 \r
179 static unsigned long input2_read(unsigned long interesting, int *joy)\r
180 {\r
181         unsigned long ret;\r
182         int i;\r
183 \r
184         do\r
185         {\r
186                 *joy = 0;\r
187                 if ((ret = gp2x_joystick_read(0) & interesting)) break;\r
188                 gp2x_usbjoy_update();\r
189                 for (i = 0; i < num_of_joys; i++) {\r
190                         ret = gp2x_usbjoy_check2(i);\r
191                         if (ret) { *joy = i + 1; break; }\r
192                 }\r
193                 if (ret) break;\r
194         }\r
195         while(0);\r
196 \r
197         return ret;\r
198 }\r
199 \r
200 // similar to wait_for_input(), but returns joy num\r
201 static unsigned long wait_for_input_usbjoy(unsigned long interesting, int *joy)\r
202 {\r
203         unsigned long ret;\r
204         const int wait = 300*1000;\r
205         int i;\r
206 \r
207         if (inp_prevjoy == 0) inp_prev &= interesting;\r
208         for (i = 0; i < 6; i++) {\r
209                 ret = input2_read(interesting, joy);\r
210                 if (*joy != inp_prevjoy || ret != inp_prev) break;\r
211                 usleep(wait/6);\r
212         }\r
213 \r
214         while ( !(ret = input2_read(interesting, joy)) ) {\r
215                 usleep(50000);\r
216         }\r
217 \r
218         inp_prev = ret;\r
219         inp_prevjoy = *joy;\r
220 \r
221         return ret;\r
222 }\r
223 \r
224 \r
225 \r
226 // -------------- ROM selector --------------\r
227 \r
228 static void draw_dirlist(char *curdir, struct dirent **namelist, int n, int sel)\r
229 {\r
230         int start, i, pos;\r
231 \r
232         start = 12 - sel;\r
233         n--; // exclude current dir (".")\r
234 \r
235         //memset(gp2x_screen, 0, 320*240);\r
236         gp2x_fceu_copy_bg();\r
237 \r
238         if(start - 2 >= 0)\r
239                 gp2x_smalltext8_lim(14, (start - 2)*10, curdir, 53-2);\r
240         for (i = 0; i < n; i++) {\r
241                 pos = start + i;\r
242                 if (pos < 0)  continue;\r
243                 if (pos > 23) break;\r
244                 if (namelist[i+1]->d_type == DT_DIR) {\r
245                         gp2x_smalltext8_lim(14,   pos*10, "/", 1);\r
246                         gp2x_smalltext8_lim(14+6, pos*10, namelist[i+1]->d_name, 53-3);\r
247                 } else {\r
248                         gp2x_smalltext8_lim(14,   pos*10, namelist[i+1]->d_name, 53-2);\r
249                 }\r
250         }\r
251         gp2x_text_out15(5, 120, ">");\r
252         gp2x_video_flip();\r
253 }\r
254 \r
255 static int scandir_cmp(const void *p1, const void *p2)\r
256 {\r
257         struct dirent **d1 = (struct dirent **)p1, **d2 = (struct dirent **)p2;\r
258         if ((*d1)->d_type == (*d2)->d_type) return alphasort(d1, d2);\r
259         if ((*d1)->d_type == DT_DIR) return -1; // put before\r
260         if ((*d2)->d_type == DT_DIR) return  1;\r
261         return alphasort(d1, d2);\r
262 }\r
263 \r
264 static char *filter_exts[] = {\r
265         // TODO\r
266         ".mp3", ".MP3", ".srm", ".brm", "s.gz", ".mds", "bcfg", ".txt", ".htm", "html",\r
267         ".jpg", ".gpe", ".cue"\r
268 };\r
269 \r
270 static int scandir_filter(const struct dirent *ent)\r
271 {\r
272         const char *p;\r
273         int i;\r
274 \r
275         if (ent == NULL || ent->d_name == NULL) return 0;\r
276         if (strlen(ent->d_name) < 5) return 1;\r
277 \r
278         p = ent->d_name + strlen(ent->d_name) - 4;\r
279 \r
280         for (i = 0; i < sizeof(filter_exts)/sizeof(filter_exts[0]); i++)\r
281         {\r
282                 if (strcmp(p, filter_exts[i]) == 0) return 0;\r
283         }\r
284 \r
285         return 1;\r
286 }\r
287 \r
288 static char *romsel_loop(char *curr_path)\r
289 {\r
290         struct dirent **namelist;\r
291         DIR *dir;\r
292         int n, sel = 0;\r
293         unsigned long inp = 0;\r
294         char *ret = NULL, *fname = NULL;\r
295 \r
296         // is this a dir or a full path?\r
297         if ((dir = opendir(curr_path))) {\r
298                 closedir(dir);\r
299         } else {\r
300                 char *p;\r
301                 for (p = curr_path + strlen(curr_path) - 1; p > curr_path && *p != '/'; p--);\r
302                 *p = 0;\r
303                 fname = p+1;\r
304         }\r
305 \r
306         n = scandir(curr_path, &namelist, scandir_filter, scandir_cmp);\r
307         if (n < 0) {\r
308                 // try root\r
309                 n = scandir("/", &namelist, scandir_filter, scandir_cmp);\r
310                 if (n < 0) {\r
311                         // oops, we failed\r
312                         printf("dir: %s\n", curr_path);\r
313                         perror("scandir");\r
314                         return NULL;\r
315                 }\r
316         }\r
317 \r
318         // try to find sel\r
319         if (fname != NULL) {\r
320                 int i;\r
321                 for (i = 1; i < n; i++) {\r
322                         if (strcmp(namelist[i]->d_name, fname) == 0) {\r
323                                 sel = i - 1;\r
324                                 break;\r
325                         }\r
326                 }\r
327         }\r
328 \r
329         for (;;)\r
330         {\r
331                 draw_dirlist(curr_path, namelist, n, sel);\r
332                 inp = wait_for_input(GP2X_UP|GP2X_DOWN|GP2X_LEFT|GP2X_RIGHT|GP2X_L|GP2X_R|GP2X_B|GP2X_X);\r
333                 if(inp & GP2X_UP  )  { sel--;   if (sel < 0)   sel = n-2; }\r
334                 if(inp & GP2X_DOWN)  { sel++;   if (sel > n-2) sel = 0; }\r
335                 if(inp & GP2X_LEFT)  { sel-=10; if (sel < 0)   sel = 0; }\r
336                 if(inp & GP2X_L)     { sel-=24; if (sel < 0)   sel = 0; }\r
337                 if(inp & GP2X_RIGHT) { sel+=10; if (sel > n-2) sel = n-2; }\r
338                 if(inp & GP2X_R)     { sel+=24; if (sel > n-2) sel = n-2; }\r
339                 if(inp & GP2X_B)     { // enter dir/select\r
340                         again:\r
341                         if (namelist[sel+1]->d_type == DT_REG) {\r
342                                 strcpy(lastLoadedGameName, curr_path);\r
343                                 strcat(lastLoadedGameName, "/");\r
344                                 strcat(lastLoadedGameName, namelist[sel+1]->d_name);\r
345                                 ret = lastLoadedGameName;\r
346                                 break;\r
347                         } else if (namelist[sel+1]->d_type == DT_DIR) {\r
348                                 int newlen = strlen(curr_path) + strlen(namelist[sel+1]->d_name) + 2;\r
349                                 char *p, *newdir = malloc(newlen);\r
350                                 if (strcmp(namelist[sel+1]->d_name, "..") == 0) {\r
351                                         char *start = curr_path;\r
352                                         p = start + strlen(start) - 1;\r
353                                         while (*p == '/' && p > start) p--;\r
354                                         while (*p != '/' && p > start) p--;\r
355                                         if (p <= start) strcpy(newdir, "/");\r
356                                         else { strncpy(newdir, start, p-start); newdir[p-start] = 0; }\r
357                                 } else {\r
358                                         strcpy(newdir, curr_path);\r
359                                         p = newdir + strlen(newdir) - 1;\r
360                                         while (*p == '/' && p >= newdir) *p-- = 0;\r
361                                         strcat(newdir, "/");\r
362                                         strcat(newdir, namelist[sel+1]->d_name);\r
363                                 }\r
364                                 ret = romsel_loop(newdir);\r
365                                 free(newdir);\r
366                                 break;\r
367                         } else {\r
368                                 // unknown file type, happens on NTFS mounts. Try to guess.\r
369                                 FILE *tstf; int tmp;\r
370                                 strcpy(lastLoadedGameName, curr_path);\r
371                                 strcat(lastLoadedGameName, "/");\r
372                                 strcat(lastLoadedGameName, namelist[sel+1]->d_name);\r
373                                 tstf = fopen(lastLoadedGameName, "rb");\r
374                                 if (tstf != NULL)\r
375                                 {\r
376                                         if (fread(&tmp, 1, 1, tstf) > 0 || ferror(tstf) == 0)\r
377                                                 namelist[sel+1]->d_type = DT_REG;\r
378                                         else    namelist[sel+1]->d_type = DT_DIR;\r
379                                         fclose(tstf);\r
380                                         goto again;\r
381                                 }\r
382                         }\r
383                 }\r
384                 if(inp & GP2X_X) break; // cancel\r
385         }\r
386 \r
387         if (n > 0) {\r
388                 while(n--) free(namelist[n]);\r
389                 free(namelist);\r
390         }\r
391 \r
392         return ret;\r
393 }\r
394 \r
395 // ------------ patch/gg menu ------------\r
396 \r
397 #if 0 // TODO?\r
398 static void draw_patchlist(int sel)\r
399 {\r
400         int start, i, pos;\r
401 \r
402         start = 12 - sel;\r
403 \r
404         gp2x_fceu_copy_bg();\r
405 \r
406         for (i = 0; i < PicoPatchCount; i++) {\r
407                 pos = start + i;\r
408                 if (pos < 0)  continue;\r
409                 if (pos > 23) break;\r
410                 gp2x_smalltext8_lim(14,     pos*10, PicoPatches[i].active ? "ON " : "OFF", 3);\r
411                 gp2x_smalltext8_lim(14+6*4, pos*10, PicoPatches[i].name, 53-6);\r
412         }\r
413         pos = start + i;\r
414         if (pos < 24) gp2x_smalltext8_lim(14, pos*10, "done", 4);\r
415 \r
416         gp2x_text_out15(5, 120, ">");\r
417         gp2x_video_flip();\r
418 }\r
419 \r
420 \r
421 void patches_menu_loop(void)\r
422 {\r
423         int menu_sel = 0;\r
424         unsigned long inp = 0;\r
425 \r
426         for(;;)\r
427         {\r
428                 draw_patchlist(menu_sel);\r
429                 inp = wait_for_input(GP2X_UP|GP2X_DOWN|GP2X_LEFT|GP2X_RIGHT|GP2X_L|GP2X_R|GP2X_B|GP2X_X);\r
430                 if(inp & GP2X_UP  ) { menu_sel--; if (menu_sel < 0) menu_sel = PicoPatchCount; }\r
431                 if(inp & GP2X_DOWN) { menu_sel++; if (menu_sel > PicoPatchCount) menu_sel = 0; }\r
432                 if(inp &(GP2X_LEFT|GP2X_L))  { menu_sel-=10; if (menu_sel < 0) menu_sel = 0; }\r
433                 if(inp &(GP2X_RIGHT|GP2X_R)) { menu_sel+=10; if (menu_sel > PicoPatchCount) menu_sel = PicoPatchCount; }\r
434                 if(inp & GP2X_B) { // action\r
435                         if (menu_sel < PicoPatchCount)\r
436                                 PicoPatches[menu_sel].active = !PicoPatches[menu_sel].active;\r
437                         else    return;\r
438                 }\r
439                 if(inp & GP2X_X) return;\r
440         }\r
441 \r
442 }\r
443 \r
444 // ------------ savestate loader ------------\r
445 \r
446 static void menu_prepare_bg(void);\r
447 \r
448 static int state_slot_flags = 0;\r
449 \r
450 static void state_check_slots(void)\r
451 {\r
452         int slot;\r
453 \r
454         state_slot_flags = 0;\r
455 \r
456         for (slot = 0; slot < 10; slot++)\r
457         {\r
458                 if (emu_check_save_file(slot))\r
459                 {\r
460                         state_slot_flags |= 1 << slot;\r
461                 }\r
462         }\r
463 }\r
464 \r
465 static void draw_savestate_bg(int slot)\r
466 {\r
467         struct PicoVideo tmp_pv;\r
468         unsigned short tmp_cram[0x40];\r
469         unsigned short tmp_vsram[0x40];\r
470         void *tmp_vram, *file;\r
471         char *fname;\r
472 \r
473         fname = emu_GetSaveFName(1, 0, slot);\r
474         if (!fname) return;\r
475 \r
476         tmp_vram = malloc(sizeof(Pico.vram));\r
477         if (tmp_vram == NULL) return;\r
478 \r
479         memcpy(tmp_vram, Pico.vram, sizeof(Pico.vram));\r
480         memcpy(tmp_cram, Pico.cram, sizeof(Pico.cram));\r
481         memcpy(tmp_vsram, Pico.vsram, sizeof(Pico.vsram));\r
482         memcpy(&tmp_pv, &Pico.video, sizeof(Pico.video));\r
483 \r
484         if (strcmp(fname + strlen(fname) - 3, ".gz") == 0) {\r
485                 file = gzopen(fname, "rb");\r
486                 emu_set_save_cbs(1);\r
487         } else {\r
488                 file = fopen(fname, "rb");\r
489                 emu_set_save_cbs(0);\r
490         }\r
491 \r
492         if (file) {\r
493                 if (PicoMCD & 1) {\r
494                         PicoCdLoadStateGfx(file);\r
495                 } else {\r
496                         areaSeek(file, 0x10020, SEEK_SET);  // skip header and RAM in state file\r
497                         areaRead(Pico.vram, 1, sizeof(Pico.vram), file);\r
498                         areaSeek(file, 0x2000, SEEK_CUR);\r
499                         areaRead(Pico.cram, 1, sizeof(Pico.cram), file);\r
500                         areaRead(Pico.vsram, 1, sizeof(Pico.vsram), file);\r
501                         areaSeek(file, 0x221a0, SEEK_SET);\r
502                         areaRead(&Pico.video, 1, sizeof(Pico.video), file);\r
503                 }\r
504                 areaClose(file);\r
505         }\r
506 \r
507         emu_forced_frame();\r
508         gp2x_memcpy_buffers((1<<2), gp2x_screen, 0, 320*240*2);\r
509         menu_prepare_bg();\r
510 \r
511         memcpy(Pico.vram, tmp_vram, sizeof(Pico.vram));\r
512         memcpy(Pico.cram, tmp_cram, sizeof(Pico.cram));\r
513         memcpy(Pico.vsram, tmp_vsram, sizeof(Pico.vsram));\r
514         memcpy(&Pico.video, &tmp_pv,  sizeof(Pico.video));\r
515         free(tmp_vram);\r
516 }\r
517 \r
518 static void draw_savestate_menu(int menu_sel, int is_loading)\r
519 {\r
520         int tl_x = 25, tl_y = 60, y, i;\r
521 \r
522         if (state_slot_flags & (1 << menu_sel))\r
523                 draw_savestate_bg(menu_sel);\r
524         gp2x_fceu_copy_bg();\r
525 \r
526         gp2x_text_out15(tl_x, 30, is_loading ? "Load state" : "Save state");\r
527 \r
528         /* draw all 10 slots */\r
529         y = tl_y;\r
530         for (i = 0; i < 10; i++, y+=10)\r
531         {\r
532                 gp2x_text_out15(tl_x, y, "SLOT %i (%s)", i, (state_slot_flags & (1 << i)) ? "USED" : "free");\r
533         }\r
534         gp2x_text_out15(tl_x, y, "back");\r
535 \r
536         // draw cursor\r
537         gp2x_text_out15(tl_x - 16, tl_y + menu_sel*10, ">");\r
538 \r
539         gp2x_video_flip();\r
540 }\r
541 \r
542 static int savestate_menu_loop(int is_loading)\r
543 {\r
544         int menu_sel = 10, menu_sel_max = 10;\r
545         unsigned long inp = 0;\r
546 \r
547         state_check_slots();\r
548 \r
549         for(;;)\r
550         {\r
551                 draw_savestate_menu(menu_sel, is_loading);\r
552                 inp = wait_for_input(GP2X_UP|GP2X_DOWN|GP2X_B|GP2X_X);\r
553                 if(inp & GP2X_UP  ) {\r
554                         do {\r
555                                 menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max;\r
556                         } while (!(state_slot_flags & (1 << menu_sel)) && menu_sel != menu_sel_max && is_loading);\r
557                 }\r
558                 if(inp & GP2X_DOWN) {\r
559                         do {\r
560                                 menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0;\r
561                         } while (!(state_slot_flags & (1 << menu_sel)) && menu_sel != menu_sel_max && is_loading);\r
562                 }\r
563                 if(inp & GP2X_B) { // save/load\r
564                         if (menu_sel < 10) {\r
565                                 state_slot = menu_sel;\r
566                                 if (emu_SaveLoadGame(is_loading, 0)) {\r
567                                         strcpy(menuErrorMsg, is_loading ? "Load failed" : "Save failed");\r
568                                         return 1;\r
569                                 }\r
570                                 return 0;\r
571                         } else  return 1;\r
572                 }\r
573                 if(inp & GP2X_X) return 1;\r
574         }\r
575 }\r
576 \r
577 // -------------- key config --------------\r
578 \r
579 static char *usb_joy_key_name(int joy, int num)\r
580 {\r
581         static char name[16];\r
582         switch (num)\r
583         {\r
584                 case 0: sprintf(name, "Joy%i UP", joy); break;\r
585                 case 1: sprintf(name, "Joy%i DOWN", joy); break;\r
586                 case 2: sprintf(name, "Joy%i LEFT", joy); break;\r
587                 case 3: sprintf(name, "Joy%i RIGHT", joy); break;\r
588                 default:sprintf(name, "Joy%i b%i", joy, num-3); break;\r
589         }\r
590         return name;\r
591 }\r
592 \r
593 static void draw_key_config(int curr_act, int is_p2)\r
594 {\r
595         char strkeys[32*5];\r
596         int joy, i;\r
597 \r
598         strkeys[0] = 0;\r
599         for (i = 0; i < 32; i++)\r
600         {\r
601                 if (currentConfig.KeyBinds[i] & (1 << curr_act))\r
602                 {\r
603                         if (curr_act < 16 && (currentConfig.KeyBinds[i] & (1 << 16)) != (is_p2 << 16)) continue;\r
604                         if (strkeys[0]) { strcat(strkeys, " + "); strcat(strkeys, gp2xKeyNames[i]); break; }\r
605                         else strcpy(strkeys, gp2xKeyNames[i]);\r
606                 }\r
607         }\r
608         for (joy = 0; joy < num_of_joys; joy++)\r
609         {\r
610                 for (i = 0; i < 32; i++)\r
611                 {\r
612                         if (currentConfig.JoyBinds[joy][i] & (1 << curr_act))\r
613                         {\r
614                                 if (curr_act < 16 && (currentConfig.JoyBinds[joy][i] & (1 << 16)) != (is_p2 << 16)) continue;\r
615                                 if (strkeys[0]) {\r
616                                         strcat(strkeys, ", "); strcat(strkeys, usb_joy_key_name(joy + 1, i));\r
617                                         break;\r
618                                 }\r
619                                 else strcpy(strkeys, usb_joy_key_name(joy + 1, i));\r
620                         }\r
621                 }\r
622         }\r
623 \r
624         //memset(gp2x_screen, 0, 320*240);\r
625         gp2x_fceu_copy_bg();\r
626         gp2x_text_out15(60, 40, "Action: %s", actionNames[curr_act]);\r
627         gp2x_text_out15(60, 60, "Keys: %s", strkeys);\r
628 \r
629         gp2x_text_out15(30, 180, "Use SELECT to change action");\r
630         gp2x_text_out15(30, 190, "Press a key to bind/unbind");\r
631         gp2x_text_out15(30, 200, "Select \"Done\" action and");\r
632         gp2x_text_out15(30, 210, "  press any key to finish");\r
633         gp2x_video_flip();\r
634 }\r
635 \r
636 static void key_config_loop(int is_p2)\r
637 {\r
638         int curr_act = 0, joy = 0, i;\r
639         unsigned long inp = 0;\r
640 \r
641         for (;;)\r
642         {\r
643                 draw_key_config(curr_act, is_p2);\r
644                 inp = wait_for_input_usbjoy(CONFIGURABLE_KEYS, &joy);\r
645                 // printf("got %08lX from joy %i\n", inp, joy);\r
646                 if (joy == 0) {\r
647                         if (inp & GP2X_SELECT) {\r
648                                 curr_act++;\r
649                                 while (!actionNames[curr_act] && curr_act < 32) curr_act++;\r
650                                 if (curr_act > 31) curr_act = 0;\r
651                         }\r
652                         inp &= CONFIGURABLE_KEYS;\r
653                         inp &= ~GP2X_SELECT;\r
654                 }\r
655                 if (curr_act == 31 && inp) break;\r
656                 if (joy == 0) {\r
657                         for (i = 0; i < 32; i++)\r
658                                 if (inp & (1 << i)) {\r
659                                         currentConfig.KeyBinds[i] ^= (1 << curr_act);\r
660                                         if (is_p2) currentConfig.KeyBinds[i] |=  (1 << 16); // player 2 flag\r
661                                         else       currentConfig.KeyBinds[i] &= ~(1 << 16);\r
662                                 }\r
663                 } else {\r
664                         for (i = 0; i < 32; i++)\r
665                                 if (inp & (1 << i)) {\r
666                                         currentConfig.JoyBinds[joy-1][i] ^= (1 << curr_act);\r
667                                         if (is_p2) currentConfig.JoyBinds[joy-1][i] |=  (1 << 16);\r
668                                         else       currentConfig.JoyBinds[joy-1][i] &= ~(1 << 16);\r
669                                 }\r
670                 }\r
671         }\r
672 }\r
673 \r
674 static void draw_kc_sel(int menu_sel)\r
675 {\r
676         int tl_x = 25+40, tl_y = 60, y, i;\r
677         char joyname[36];\r
678 \r
679         y = tl_y;\r
680         //memset(gp2x_screen, 0, 320*240);\r
681         gp2x_fceu_copy_bg();\r
682         gp2x_text_out15(tl_x, y,       "Player 1");\r
683         gp2x_text_out15(tl_x, (y+=10), "Player 2");\r
684         gp2x_text_out15(tl_x, (y+=10), "Done");\r
685 \r
686         // draw cursor\r
687         gp2x_text_out15(tl_x - 16, tl_y + menu_sel*10, ">");\r
688 \r
689         tl_x = 25;\r
690         gp2x_text_out15(tl_x, (y=110), "USB joys detected:");\r
691         if (num_of_joys > 0) {\r
692                 for (i = 0; i < num_of_joys; i++) {\r
693                         strncpy(joyname, joy_name(joys[i]), 33); joyname[33] = 0;\r
694                         gp2x_text_out15(tl_x, (y+=10), "%i: %s", i+1, joyname);\r
695                 }\r
696         } else {\r
697                 gp2x_text_out15(tl_x, (y+=10), "none");\r
698         }\r
699 \r
700 \r
701         gp2x_video_flip();\r
702 }\r
703 \r
704 static void kc_sel_loop(void)\r
705 {\r
706         int menu_sel = 2, menu_sel_max = 2;\r
707         unsigned long inp = 0;\r
708 \r
709         for(;;)\r
710         {\r
711                 draw_kc_sel(menu_sel);\r
712                 inp = wait_for_input(GP2X_UP|GP2X_DOWN|GP2X_B|GP2X_X);\r
713                 if(inp & GP2X_UP  ) { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; }\r
714                 if(inp & GP2X_DOWN) { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; }\r
715                 if(inp & GP2X_B) {\r
716                         switch (menu_sel) {\r
717                                 case 0: key_config_loop(0); return;\r
718                                 case 1: key_config_loop(1); return;\r
719                                 default: return;\r
720                         }\r
721                 }\r
722                 if(inp & GP2X_X) return;\r
723         }\r
724 }\r
725 #endif\r
726 \r
727 \r
728 \r
729 // --------- advanced options ----------\r
730 #if 0\r
731 static void draw_amenu_options(int menu_sel)\r
732 {\r
733         int tl_x = 25, tl_y = 60, y;\r
734         char *mms = mmuhack_status ? "active)  " : "inactive)";\r
735 \r
736         y = tl_y;\r
737         //memset(gp2x_screen, 0, 320*240);\r
738         gp2x_fceu_copy_bg();\r
739 \r
740         gp2x_text_out15(tl_x, y,       "Gamma correction           %i.%02i", currentConfig.gamma / 100, currentConfig.gamma%100); // 0\r
741         gp2x_text_out15(tl_x, (y+=10), "Don't save last used ROM   %s", (currentConfig.EmuOpt &0x020)?"ON":"OFF"); // 5\r
742         gp2x_text_out15(tl_x, (y+=10), "needs restart:");\r
743         gp2x_text_out15(tl_x, (y+=10), "craigix's RAM timings      %s", (currentConfig.EmuOpt &0x100)?"ON":"OFF"); // 7\r
744         gp2x_text_out15(tl_x, (y+=10), "squidgehack (now %s %s",   mms, (currentConfig.EmuOpt &0x010)?"ON":"OFF"); // 8\r
745         gp2x_text_out15(tl_x, (y+=10), "Done");\r
746 \r
747         // draw cursor\r
748         gp2x_text_out15(tl_x - 16, tl_y + menu_sel*10, ">");\r
749 \r
750         gp2x_video_flip();\r
751 }\r
752 \r
753 static void amenu_loop_options(void)\r
754 {\r
755         int menu_sel = 0, menu_sel_max = 9;\r
756         unsigned long inp = 0;\r
757 \r
758         for(;;)\r
759         {\r
760                 draw_amenu_options(menu_sel);\r
761                 inp = wait_for_input(GP2X_UP|GP2X_DOWN|GP2X_LEFT|GP2X_RIGHT|GP2X_B|GP2X_X|GP2X_A);\r
762                 if(inp & GP2X_UP  ) { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; }\r
763                 if(inp & GP2X_DOWN) { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; }\r
764                 if((inp& GP2X_B)||(inp&GP2X_LEFT)||(inp&GP2X_RIGHT)) { // toggleable options\r
765                         switch (menu_sel) {\r
766                                 case  1: break;\r
767                                 case  9: return;\r
768                         }\r
769                 }\r
770                 if(inp & (GP2X_X|GP2X_A)) return;\r
771                 if(inp & (GP2X_LEFT|GP2X_RIGHT)) { // multi choise\r
772                         switch (menu_sel) {\r
773                                 case 0:\r
774                                         while ((inp = gp2x_joystick_read(1)) & (GP2X_LEFT|GP2X_RIGHT)) {\r
775                                                 currentConfig.gamma += (inp & GP2X_LEFT) ? -1 : 1;\r
776                                                 if (currentConfig.gamma <   1) currentConfig.gamma =   1;\r
777                                                 if (currentConfig.gamma > 300) currentConfig.gamma = 300;\r
778                                                 draw_amenu_options(menu_sel);\r
779                                                 usleep(18*1000);\r
780                                         }\r
781                                         break;\r
782                         }\r
783                 }\r
784         }\r
785 }\r
786 #endif\r
787 \r
788 // -------------- options --------------\r
789 \r
790 static void draw_menu_options(int menu_sel)\r
791 {\r
792         int tl_x = 25, tl_y = 32, y;\r
793         char monostereo[8], strframeskip[8], *strscaling, *strssconfirm;\r
794 \r
795         //strcpy(monostereo, (currentConfig.PicoOpt&0x08)?"stereo":"mono");\r
796         if (Settings.frameskip < 0)\r
797              strcpy(strframeskip, "Auto");\r
798         else sprintf(strframeskip, "%i", Settings.frameskip);\r
799         switch (Settings.scaling) {\r
800                 default: strscaling = "            OFF";   break;\r
801                 case 1:  strscaling = "hw horizontal";     break;\r
802                 case 2:  strscaling = "hw horiz. + vert."; break;\r
803                 case 3:  strscaling = "sw horizontal";     break;\r
804         }\r
805         switch (Settings.sstate_confirm) {\r
806                 default: strssconfirm = "OFF";    break;\r
807                 case 1:  strssconfirm = "writes"; break;\r
808                 case 2:  strssconfirm = "loads";  break;\r
809                 case 3:  strssconfirm = "both";   break;\r
810         }\r
811 \r
812         y = tl_y;\r
813         //memset(gp2x_screen, 0, 320*240);\r
814         gp2x_fceu_copy_bg();\r
815 \r
816         gp2x_text_out15(tl_x, (y+=10), "Scaling:       %s", strscaling);                                // 0\r
817         gp2x_text_out15(tl_x, (y+=10), "Show FPS                   %s", Settings.showfps?"ON":"OFF");   // 1\r
818         gp2x_text_out15(tl_x, (y+=10), "Frameskip                  %s", strframeskip);                  // 2\r
819         gp2x_text_out15(tl_x, (y+=10), "Enable sound               %s", /*(currentConfig.EmuOpt &0x004)?"ON":*/"OFF"); // 3\r
820         gp2x_text_out15(tl_x, (y+=10), "Sound Quality:     %5iHz %s",   0, "" /*currentConfig.PsndRate, monostereo*/); // 4\r
821         gp2x_text_out15(tl_x, (y+=10), "Region:              %s",\r
822                 Settings.region_force == 2 ? "NTSC" : Settings.region_force == 1 ? "PAL" : "OFF");      // 5\r
823         gp2x_text_out15(tl_x, (y+=10), "Use SRAM savestates        %s", "OFF");\r
824         gp2x_text_out15(tl_x, (y+=10), "Confirm savestate          %s", strssconfirm);\r
825         gp2x_text_out15(tl_x, (y+=10), "Save slot                  %i", 0/*state_slot*/);               // 8\r
826         gp2x_text_out15(tl_x, (y+=10), "GP2X CPU clock             %iMhz", Settings.cpuclock);\r
827         gp2x_text_out15(tl_x, (y+=10), "[advanced options]");                                           // 10\r
828         gp2x_text_out15(tl_x, (y+=10), "Save cfg as default");\r
829         if (fceugi)\r
830                 gp2x_text_out15(tl_x, (y+=10), "Save cfg for current game only");\r
831 \r
832         // draw cursor\r
833         gp2x_text_out15(tl_x - 16, tl_y + menu_sel*10, ">");\r
834 \r
835         gp2x_video_flip();\r
836 }\r
837 \r
838 /*\r
839 static int sndrate_prevnext(int rate, int dir)\r
840 {\r
841         int i, rates[] = { 8000, 11025, 16000, 22050, 44100 };\r
842 \r
843         for (i = 0; i < 5; i++)\r
844                 if (rates[i] == rate) break;\r
845 \r
846         i += dir ? 1 : -1;\r
847         if (i > 4) return dir ? 44100 : 22050;\r
848         if (i < 0) return dir ? 11025 : 8000;\r
849         return rates[i];\r
850 }\r
851 */\r
852 static void int_incdec(int *p, int inc, int min, int max)\r
853 {\r
854         *p += inc;\r
855         if      (*p < min) *p = min;\r
856         else if (*p > max) *p = max;\r
857 }\r
858 \r
859 static int menu_loop_options(void)\r
860 {\r
861         int menu_sel = 0, menu_sel_max = 11;\r
862         unsigned long inp = 0;\r
863 \r
864         if (fceugi) menu_sel_max++;\r
865 \r
866         for(;;)\r
867         {\r
868                 draw_menu_options(menu_sel);\r
869                 inp = wait_for_input(GP2X_UP|GP2X_DOWN|GP2X_LEFT|GP2X_RIGHT|GP2X_B|GP2X_X|GP2X_A);\r
870                 if(inp & GP2X_UP  ) { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; }\r
871                 if(inp & GP2X_DOWN) { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; }\r
872                 if((inp& GP2X_B)||(inp&GP2X_LEFT)||(inp&GP2X_RIGHT)) { // toggleable options\r
873                         switch (menu_sel) {\r
874                                 case  1: Settings.showfps = !Settings.showfps; break;\r
875                                 //case  6: Settings. = !Settings.showfps; break;\r
876                                 //case 16: amenu_loop_options();    break;\r
877                                 case 17: // done (update and write)\r
878                                         //if (emu_WriteConfig(0)) strcpy(menuErrorMsg, "config saved");\r
879                                         //else strcpy(menuErrorMsg, "failed to write config");\r
880                                         return 1;\r
881                                 case 18: // done (update and write for current game)\r
882                                         //if (emu_WriteConfig(1)) strcpy(menuErrorMsg, "config saved");\r
883                                         //else strcpy(menuErrorMsg, "failed to write config");\r
884                                         return 1;\r
885                         }\r
886                 }\r
887                 if(inp & (GP2X_X|GP2X_A)) {\r
888                         return 0;  // done (update, no write)\r
889                 }\r
890                 if(inp & (GP2X_LEFT|GP2X_RIGHT)) { // multi choise\r
891                         switch (menu_sel) {\r
892                                 case  0: int_incdec(&Settings.scaling,   (inp & GP2X_LEFT) ? -1 : 1,  0,  3); break;\r
893                                 case  2: int_incdec(&Settings.frameskip, (inp & GP2X_LEFT) ? -1 : 1, -1, 32); break;\r
894 /*\r
895                                 case  4:\r
896                                         if ((inp & GP2X_RIGHT) && currentConfig.PsndRate == 44100 && !(currentConfig.PicoOpt&0x08)) {\r
897                                                 currentConfig.PsndRate = 8000;  currentConfig.PicoOpt|= 0x08;\r
898                                         } else if ((inp & GP2X_LEFT) && currentConfig.PsndRate == 8000 && (currentConfig.PicoOpt&0x08)) {\r
899                                                 currentConfig.PsndRate = 44100; currentConfig.PicoOpt&=~0x08;\r
900                                         } else currentConfig.PsndRate = sndrate_prevnext(currentConfig.PsndRate, inp & GP2X_RIGHT);\r
901                                         break;\r
902 */\r
903                                 case  5: int_incdec(&Settings.region_force,   (inp & GP2X_LEFT) ? -1 : 1, 0, 2); break;\r
904                                 case  7: int_incdec(&Settings.sstate_confirm, (inp & GP2X_LEFT) ? -1 : 1, 0, 3); break;\r
905                                 //case  8: int_incdec(&Settings., (inp & GP2X_LEFT) ? -1 : 1, 0, 9); break;\r
906                                 case 14:\r
907                                         while ((inp = gp2x_joystick_read(1)) & (GP2X_LEFT|GP2X_RIGHT)) {\r
908                                                 Settings.cpuclock += (inp & GP2X_LEFT) ? -1 : 1;\r
909                                                 if (Settings.cpuclock < 1) Settings.cpuclock = 1;\r
910                                                 draw_menu_options(menu_sel);\r
911                                                 usleep(50*1000);\r
912                                         }\r
913                                         break;\r
914                         }\r
915                 }\r
916         }\r
917 }\r
918 \r
919 // -------------- credits --------------\r
920 \r
921 static void draw_menu_credits(void)\r
922 {\r
923         int tl_x = 15, tl_y = 70, y;\r
924         //memset(gp2x_screen, 0, 320*240);\r
925         gp2x_fceu_copy_bg();\r
926 \r
927         // TODO\r
928 \r
929         gp2x_video_flip();\r
930 }\r
931 \r
932 \r
933 // -------------- root menu --------------\r
934 \r
935 static void draw_menu_root(int menu_sel)\r
936 {\r
937         int tl_x = 70, tl_y = 70, y;\r
938         gp2x_fceu_copy_bg();\r
939 \r
940         y = tl_y;\r
941         if (fceugi) {\r
942                 gp2x_text_out15(tl_x, y,       "Resume game");\r
943                 gp2x_text_out15(tl_x, (y+=10), "Save State");\r
944                 gp2x_text_out15(tl_x, (y+=10), "Load State");\r
945                 gp2x_text_out15(tl_x, (y+=10), "Reset game");\r
946         } else {\r
947                 y += 30;\r
948         }\r
949         gp2x_text_out15(tl_x, (y+=10), "Load new ROM");\r
950         gp2x_text_out15(tl_x, (y+=10), "Change options");\r
951         gp2x_text_out15(tl_x, (y+=10), "Configure controls");\r
952         gp2x_text_out15(tl_x, (y+=10), "Credits");\r
953         gp2x_text_out15(tl_x, (y+=10), "Exit");\r
954 // TODO\r
955 //      if (PicoPatches)\r
956 //              gp2x_text_out15(tl_x, (y+=10), "Patches / GameGenie");\r
957 \r
958         // draw cursor\r
959         gp2x_text_out15(tl_x - 16, tl_y + menu_sel*10, ">");\r
960         // error\r
961         if (menuErrorMsg[0]) gp2x_text_out15(5, 226, menuErrorMsg);\r
962         gp2x_video_flip();\r
963 }\r
964 \r
965 \r
966 static int menu_loop_root(void)\r
967 {\r
968         int ret, menu_sel = 4, menu_sel_max = 8, menu_sel_min = 4;\r
969         unsigned long inp = 0;\r
970         char curr_path[PATH_MAX], *selfname;\r
971         FILE *tstf;\r
972 \r
973         if ( (tstf = fopen(lastLoadedGameName, "rb")) )\r
974         {\r
975                 fclose(tstf);\r
976                 strncpy(curr_path, lastLoadedGameName, sizeof(curr_path));\r
977                 curr_path[sizeof(curr_path)-1] = 0;\r
978         }\r
979         else\r
980         {\r
981                 getcwd(curr_path, PATH_MAX);\r
982         }\r
983 \r
984         if (fceugi) menu_sel = menu_sel_min = 0;\r
985 // TODO if (PicoPatches) menu_sel_max = 9;\r
986 \r
987         /* make sure action buttons are not pressed on entering menu */\r
988         draw_menu_root(menu_sel);\r
989         while (gp2x_joystick_read(1) & (GP2X_B|GP2X_X|GP2X_SELECT)) usleep(50*1000);\r
990 \r
991         for (;;)\r
992         {\r
993                 draw_menu_root(menu_sel);\r
994                 inp = wait_for_input(GP2X_UP|GP2X_DOWN|GP2X_B|GP2X_X);\r
995                 if(inp & GP2X_UP  )  { menu_sel--; if (menu_sel < menu_sel_min) menu_sel = menu_sel_max; }\r
996                 if(inp & GP2X_DOWN)  { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = menu_sel_min; }\r
997                 if(inp &(GP2X_SELECT|GP2X_X)){\r
998                         if (fceugi) {\r
999                                 while (gp2x_joystick_read(1) & GP2X_X) usleep(50*1000); // wait until X is released\r
1000                                 Exit = 0;\r
1001                                 return 0;\r
1002                         }\r
1003                 }\r
1004                 if(inp & GP2X_B   )  {\r
1005                         switch (menu_sel) {\r
1006                                 case 0: // resume game\r
1007                                         if (fceugi) {\r
1008                                                 while (gp2x_joystick_read(1) & GP2X_B) usleep(50*1000);\r
1009                                                 Exit = 0;\r
1010                                                 return 0;\r
1011                                         }\r
1012                                         break;\r
1013                                 case 1: // save state\r
1014                                         if (fceugi) {\r
1015                                                 /*if(savestate_menu_loop(0))\r
1016                                                         continue;*/\r
1017                                                 return 0;\r
1018                                         }\r
1019                                         break;\r
1020                                 case 2: // load state\r
1021                                         if (fceugi) {\r
1022                                                 /*if(savestate_menu_loop(1))\r
1023                                                         continue;*/\r
1024                                                 return 0;\r
1025                                         }\r
1026                                         break;\r
1027                                 case 3: // reset game\r
1028                                         if (fceugi) {\r
1029                                                 return 0;\r
1030                                         }\r
1031                                         break;\r
1032                                 case 4: // select rom\r
1033                                         selfname = romsel_loop(curr_path);\r
1034                                         if (selfname) {\r
1035                                                 printf("selected file: %s\n", selfname);\r
1036                                                 return 2;\r
1037                                         }\r
1038                                         break;\r
1039                                 case 5: // options\r
1040                                         ret = menu_loop_options();\r
1041                                         if (ret == 1) continue; // status update\r
1042                                         break;\r
1043                                 case 6: // controls\r
1044                                         // TODO kc_sel_loop();\r
1045                                         break;\r
1046                                 case 7: // credits\r
1047                                         draw_menu_credits();\r
1048                                         usleep(500*1000);\r
1049                                         inp = wait_for_input(GP2X_B|GP2X_X);\r
1050                                         break;\r
1051                                 case 8: // exit\r
1052                                         return 1;\r
1053                                 case 9: // patches/gg\r
1054                                         break;\r
1055                         }\r
1056                 }\r
1057                 menuErrorMsg[0] = 0; // clear error msg\r
1058         }\r
1059 }\r
1060 \r
1061 \r
1062 static void menu_prepare_bg(void)\r
1063 {\r
1064         // TODO...\r
1065 }\r
1066 \r
1067 static void menu_gfx_prepare(void)\r
1068 {\r
1069         menu_prepare_bg();\r
1070 \r
1071         // switch bpp\r
1072         gp2x_video_changemode(16);\r
1073         gp2x_video_RGB_setscaling(0, 320, 240);\r
1074         gp2x_video_flip();\r
1075 }\r
1076 \r
1077 \r
1078 int gp2x_menu_do(void)\r
1079 {\r
1080         int ret;\r
1081 \r
1082         menu_gfx_prepare();\r
1083 \r
1084         ret = menu_loop_root();\r
1085 \r
1086         menuErrorMsg[0] = 0;\r
1087 \r
1088         return ret;\r
1089 }\r
1090 \r
1091 \r