Add copyright message to gles_video
[gpsp.git] / gui.c
1 /* gameplaySP
2  *
3  * Copyright (C) 2006 Exophase <exophase@gmail.com>
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public Licens e as
6  * published by the Free Software Foundation; either version 2 of
7  * the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18
19 #include "common.h"
20 #include "font.h"
21
22 #ifndef _WIN32_WCE
23
24 #include <sys/stat.h>
25 #include <unistd.h>
26 #include <ctype.h>
27 #include <dirent.h>
28
29 #endif
30
31 #define MAX_PATH 1024
32
33 // Blatantly stolen and trimmed from MZX (megazeux.sourceforge.net)
34
35 #ifdef GP2X_BUILD
36
37 #define FILE_LIST_ROWS 20
38 #define FILE_LIST_POSITION 5
39 #define DIR_LIST_POSITION 260
40
41 #else
42
43 #define FILE_LIST_ROWS 25
44 #define FILE_LIST_POSITION 5
45 #define DIR_LIST_POSITION (resolution_width * 3 / 4)
46
47 #endif
48
49 #ifdef PSP_BUILD
50
51 #define COLOR_BG            color16(2, 8, 10)
52
53 #define color16(red, green, blue)                                             \
54   (blue << 11) | (green << 5) | red                                           \
55
56 #else
57
58 #define COLOR_BG            color16(0, 0, 0)
59
60 #define color16(red, green, blue)                                             \
61   (red << 11) | (green << 5) | blue                                           \
62
63 #endif
64
65 #define COLOR_ROM_INFO      color16(22, 36, 26)
66 #define COLOR_ACTIVE_ITEM   color16(31, 63, 31)
67 #define COLOR_INACTIVE_ITEM color16(13, 40, 18)
68 #define COLOR_FRAMESKIP_BAR color16(15, 31, 31)
69 #define COLOR_HELP_TEXT     color16(16, 40, 24)
70
71 #ifdef PSP_BUILD
72   static const char *clock_speed_options[] =
73   {
74     "33MHz", "66MHz", "100MHz", "133MHz", "166MHz", "200MHz", "233MHz",
75     "266MHz", "300MHz", "333MHz"
76   };
77   #define menu_get_clock_speed() \
78     clock_speed = (clock_speed_number + 1) * 33
79   #define get_clock_speed_number() \
80     clock_speed_number = (clock_speed / 33) - 1
81 #elif defined(POLLUX_BUILD)
82   static const char *clock_speed_options[] =
83   {
84     "300MHz", "333MHz", "366MHz", "400MHz", "433MHz",
85     "466MHz", "500MHz", "533MHz", "566MHz", "600MHz",
86     "633MHz", "666MHz", "700MHz", "733MHz", "766MHz",
87     "800MHz", "833MHz", "866MHz", "900MHz"
88   };
89   #define menu_get_clock_speed() \
90     clock_speed = 300 + (clock_speed_number * 3333) / 100
91   #define get_clock_speed_number() \
92     clock_speed_number = (clock_speed - 300) / 33
93 #elif defined(GP2X_BUILD)
94   static const char *clock_speed_options[] =
95   {
96     "150MHz", "160MHz", "170MHz", "180MHz", "190MHz",
97     "200MHz", "210MHz", "220MHz", "230MHz", "240MHz",
98     "250MHz", "260MHz", "270MHz", "280MHz", "290MHz"
99   };
100   #define menu_get_clock_speed() \
101     clock_speed = 150 + clock_speed_number * 10
102   #define get_clock_speed_number() \
103     clock_speed_number = (clock_speed - 150) / 10
104 #else
105   static const char *clock_speed_options[] =
106   {
107     "0"
108   };
109   #define menu_get_clock_speed()
110   #define get_clock_speed_number()
111 #endif
112
113
114 int sort_function(const void *dest_str_ptr, const void *src_str_ptr)
115 {
116   char *dest_str = *((char **)dest_str_ptr);
117   char *src_str = *((char **)src_str_ptr);
118
119   if(src_str[0] == '.')
120     return 1;
121
122   if(dest_str[0] == '.')
123     return -1;
124
125   return strcasecmp(dest_str, src_str);
126 }
127
128 s32 load_file(const char **wildcards, char *result)
129 {
130   DIR *current_dir;
131   struct dirent *current_file;
132   struct stat file_info;
133   char current_dir_name[MAX_PATH];
134   char current_dir_short[81];
135   u32 current_dir_length;
136   u32 total_filenames_allocated;
137   u32 total_dirnames_allocated;
138   char **file_list;
139   char **dir_list;
140   u32 num_files;
141   u32 num_dirs;
142   char *file_name;
143   u32 file_name_length;
144   u32 ext_pos = -1;
145   u32 chosen_file, chosen_dir;
146   s32 return_value = 1;
147   s32 current_file_selection;
148   s32 current_file_scroll_value;
149   u32 current_dir_selection;
150   u32 current_dir_scroll_value;
151   s32 current_file_in_scroll;
152   u32 current_dir_in_scroll;
153   u32 current_file_number, current_dir_number;
154   u32 current_column = 0;
155   u32 repeat;
156   u32 i;
157   gui_action_type gui_action;
158
159   while(return_value == 1)
160   {
161     current_file_selection = 0;
162     current_file_scroll_value = 0;
163     current_dir_selection = 0;
164     current_dir_scroll_value = 0;
165     current_file_in_scroll = 0;
166     current_dir_in_scroll = 0;
167
168     total_filenames_allocated = 32;
169     total_dirnames_allocated = 32;
170     file_list = (char **)malloc(sizeof(char *) * 32);
171     dir_list = (char **)malloc(sizeof(char *) * 32);
172     memset(file_list, 0, sizeof(char *) * 32);
173     memset(dir_list, 0, sizeof(char *) * 32);
174
175     num_files = 0;
176     num_dirs = 0;
177     chosen_file = 0;
178     chosen_dir = 0;
179
180     getcwd(current_dir_name, MAX_PATH);
181
182     current_dir = opendir(current_dir_name);
183
184     do
185     {
186       if(current_dir)
187         current_file = readdir(current_dir);
188       else
189         current_file = NULL;
190
191       if(current_file)
192       {
193         file_name = current_file->d_name;
194         file_name_length = strlen(file_name);
195
196         if((stat(file_name, &file_info) >= 0) &&
197          ((file_name[0] != '.') || (file_name[1] == '.')))
198         {
199           if(S_ISDIR(file_info.st_mode))
200           {
201             dir_list[num_dirs] = malloc(file_name_length + 1);
202
203             sprintf(dir_list[num_dirs], "%s", file_name);
204
205             num_dirs++;
206           }
207           else
208           {
209             // Must match one of the wildcards, also ignore the .
210             if(file_name_length >= 4)
211             {
212               if(file_name[file_name_length - 4] == '.')
213                 ext_pos = file_name_length - 4;
214               else
215
216               if(file_name[file_name_length - 3] == '.')
217                 ext_pos = file_name_length - 3;
218
219               else
220                 ext_pos = 0;
221
222               for(i = 0; wildcards[i] != NULL; i++)
223               {
224                 if(!strcasecmp((file_name + ext_pos),
225                  wildcards[i]))
226                 {
227                   file_list[num_files] =
228                    malloc(file_name_length + 1);
229
230                   sprintf(file_list[num_files], "%s", file_name);
231
232                   num_files++;
233                   break;
234                 }
235               }
236             }
237           }
238         }
239
240         if(num_files == total_filenames_allocated)
241         {
242           file_list = (char **)realloc(file_list, sizeof(char *) *
243            total_filenames_allocated * 2);
244           memset(file_list + total_filenames_allocated, 0,
245            sizeof(char *) * total_filenames_allocated);
246           total_filenames_allocated *= 2;
247         }
248
249         if(num_dirs == total_dirnames_allocated)
250         {
251           dir_list = (char **)realloc(dir_list, sizeof(char *) *
252            total_dirnames_allocated * 2);
253           memset(dir_list + total_dirnames_allocated, 0,
254            sizeof(char *) * total_dirnames_allocated);
255           total_dirnames_allocated *= 2;
256         }
257       }
258     } while(current_file);
259
260     qsort((void *)file_list, num_files, sizeof(char *), sort_function);
261     qsort((void *)dir_list, num_dirs, sizeof(char *), sort_function);
262
263     closedir(current_dir);
264
265     current_dir_length = strlen(current_dir_name);
266
267     if(current_dir_length > 80)
268     {
269
270 #ifdef GP2X_BUILD
271     snprintf(current_dir_short, 80,
272      "...%s", current_dir_name + current_dir_length - 77);
273 #else
274     memcpy(current_dir_short, "...", 3);
275       memcpy(current_dir_short + 3,
276        current_dir_name + current_dir_length - 77, 77);
277       current_dir_short[80] = 0;
278 #endif
279     }
280     else
281     {
282 #ifdef GP2X_BUILD
283       snprintf(current_dir_short, 80, "%s", current_dir_name);
284 #else
285       memcpy(current_dir_short, current_dir_name,
286        current_dir_length + 1);
287 #endif
288     }
289
290     repeat = 1;
291
292     if(num_files == 0)
293       current_column = 1;
294
295     clear_screen(COLOR_BG);
296   {
297     while(repeat)
298     {
299       flip_screen();
300
301       print_string(current_dir_short, COLOR_ACTIVE_ITEM, COLOR_BG, 0, 0);
302 #ifdef GP2X_BUILD
303       print_string("Press X to return to the main menu.",
304        COLOR_HELP_TEXT, COLOR_BG, 20, 220);
305 #else
306       print_string("Press X to return to the main menu.",
307        COLOR_HELP_TEXT, COLOR_BG, 20, 260);
308 #endif
309
310       for(i = 0, current_file_number = i + current_file_scroll_value;
311        i < FILE_LIST_ROWS; i++, current_file_number++)
312       {
313         if(current_file_number < num_files)
314         {
315           if((current_file_number == current_file_selection) &&
316            (current_column == 0))
317           {
318             print_string(file_list[current_file_number], COLOR_ACTIVE_ITEM,
319              COLOR_BG, FILE_LIST_POSITION, ((i + 1) * 10));
320           }
321           else
322           {
323             print_string(file_list[current_file_number], COLOR_INACTIVE_ITEM,
324              COLOR_BG, FILE_LIST_POSITION, ((i + 1) * 10));
325           }
326         }
327       }
328
329       for(i = 0, current_dir_number = i + current_dir_scroll_value;
330        i < FILE_LIST_ROWS; i++, current_dir_number++)
331       {
332         if(current_dir_number < num_dirs)
333         {
334           if((current_dir_number == current_dir_selection) &&
335            (current_column == 1))
336           {
337             print_string(dir_list[current_dir_number], COLOR_ACTIVE_ITEM,
338              COLOR_BG, DIR_LIST_POSITION, ((i + 1) * 10));
339           }
340           else
341           {
342             print_string(dir_list[current_dir_number], COLOR_INACTIVE_ITEM,
343              COLOR_BG, DIR_LIST_POSITION, ((i + 1) * 10));
344           }
345         }
346       }
347
348       gui_action = get_gui_input();
349
350       switch(gui_action)
351       {
352         case CURSOR_DOWN:
353           if(current_column == 0)
354           {
355             if(current_file_selection < (num_files - 1))
356             {
357               current_file_selection++;
358               if(current_file_in_scroll == (FILE_LIST_ROWS - 1))
359               {
360                 clear_screen(COLOR_BG);
361                 current_file_scroll_value++;
362               }
363               else
364               {
365                 current_file_in_scroll++;
366               }
367             }
368             else
369             {
370               clear_screen(COLOR_BG);
371               current_file_selection = 0;
372               current_file_scroll_value = 0;
373               current_file_in_scroll = 0;
374             }
375           }
376           else
377           {
378             if(current_dir_selection < (num_dirs - 1))
379             {
380               current_dir_selection++;
381               if(current_dir_in_scroll == (FILE_LIST_ROWS - 1))
382               {
383                 clear_screen(COLOR_BG);
384                 current_dir_scroll_value++;
385               }
386               else
387               {
388                 current_dir_in_scroll++;
389               }
390             }
391           }
392
393           break;
394
395         case CURSOR_R:
396           if (current_column != 0)
397             break;
398           clear_screen(COLOR_BG);
399           current_file_selection += FILE_LIST_ROWS;
400           if (current_file_selection > num_files - 1)
401             current_file_selection = num_files - 1;
402           current_file_scroll_value = current_file_selection - FILE_LIST_ROWS / 2;
403           if (current_file_scroll_value < 0)
404           {
405             current_file_scroll_value = 0;
406             current_file_in_scroll = current_file_selection;
407           }
408           else
409           {
410             current_file_in_scroll = FILE_LIST_ROWS / 2;
411           }
412           break;
413
414         case CURSOR_UP:
415           if(current_column == 0)
416           {
417             if(current_file_selection)
418             {
419               current_file_selection--;
420               if(current_file_in_scroll == 0)
421               {
422                 clear_screen(COLOR_BG);
423                 current_file_scroll_value--;
424               }
425               else
426               {
427                 current_file_in_scroll--;
428               }
429             }
430             else
431             {
432               clear_screen(COLOR_BG);
433               current_file_selection = num_files - 1;
434               current_file_in_scroll = FILE_LIST_ROWS - 1;
435               if (current_file_in_scroll > num_files - 1)
436                 current_file_in_scroll = num_files - 1;
437               current_file_scroll_value = num_files - FILE_LIST_ROWS;
438               if (current_file_scroll_value < 0)
439                 current_file_scroll_value = 0;
440             }
441           }
442           else
443           {
444             if(current_dir_selection)
445             {
446               current_dir_selection--;
447               if(current_dir_in_scroll == 0)
448               {
449                 clear_screen(COLOR_BG);
450                 current_dir_scroll_value--;
451               }
452               else
453               {
454                 current_dir_in_scroll--;
455               }
456             }
457           }
458           break;
459
460         case CURSOR_L:
461           if (current_column != 0)
462             break;
463           clear_screen(COLOR_BG);
464           current_file_selection -= FILE_LIST_ROWS;
465           if (current_file_selection < 0)
466             current_file_selection = 0;
467           current_file_scroll_value = current_file_selection - FILE_LIST_ROWS / 2;
468           if (current_file_scroll_value < 0)
469           {
470             current_file_scroll_value = 0;
471             current_file_in_scroll = current_file_selection;
472           }
473           else
474           {
475             current_file_in_scroll = FILE_LIST_ROWS / 2;
476           }
477           break;
478
479          case CURSOR_RIGHT:
480           if(current_column == 0)
481           {
482             if(num_dirs != 0)
483               current_column = 1;
484           }
485           break;
486
487         case CURSOR_LEFT:
488           if(current_column == 1)
489           {
490             if(num_files != 0)
491               current_column = 0;
492           }
493           break;
494
495         case CURSOR_SELECT:
496           if(current_column == 1)
497           {
498             repeat = 0;
499             chdir(dir_list[current_dir_selection]);
500           }
501           else
502           {
503             if(num_files != 0)
504             {
505               repeat = 0;
506               return_value = 0;
507               strcpy(result, file_list[current_file_selection]);
508             }
509           }
510           break;
511
512         case CURSOR_BACK:
513 #ifdef PSP_BUILD
514           if(!strcmp(current_dir_name, "ms0:/PSP"))
515             break;
516 #endif
517           repeat = 0;
518           chdir("..");
519           break;
520
521         case CURSOR_EXIT:
522           return_value = -1;
523           repeat = 0;
524           break;
525
526         default:
527           break;
528       }
529     }
530   }
531
532     for(i = 0; i < num_files; i++)
533     {
534       free(file_list[i]);
535     }
536     free(file_list);
537
538     for(i = 0; i < num_dirs; i++)
539     {
540       free(dir_list[i]);
541     }
542     free(dir_list);
543   }
544
545   clear_screen(COLOR_BG);
546
547   return return_value;
548 }
549
550 typedef enum
551 {
552   NUMBER_SELECTION_OPTION = 0x01,
553   STRING_SELECTION_OPTION = 0x02,
554   SUBMENU_OPTION          = 0x04,
555   ACTION_OPTION           = 0x08,
556 } menu_option_type_enum;
557
558 struct _menu_type
559 {
560   void (* init_function)();
561   void (* passive_function)();
562   struct _menu_option_type *options;
563   u32 num_options;
564 };
565
566 struct _menu_option_type
567 {
568   void (* action_function)();
569   void (* passive_function)();
570   struct _menu_type *sub_menu;
571   const char *display_string;
572   void *options;
573   u32 *current_option;
574   u32 num_options;
575   const char *help_string;
576   u32 line_number;
577   menu_option_type_enum option_type;
578 };
579
580 typedef struct _menu_option_type menu_option_type;
581 typedef struct _menu_type menu_type;
582
583 #define make_menu(name, init_function, passive_function)                      \
584   menu_type name##_menu =                                                     \
585   {                                                                           \
586     init_function,                                                            \
587     passive_function,                                                         \
588     name##_options,                                                           \
589     sizeof(name##_options) / sizeof(menu_option_type)                         \
590   }                                                                           \
591
592 #define gamepad_config_option(display_string, number)                         \
593 {                                                                             \
594   NULL,                                                                       \
595   menu_fix_gamepad_help,                                                      \
596   NULL,                                                                       \
597   display_string ": %s",                                                      \
598   gamepad_config_buttons,                                                     \
599   gamepad_config_map + gamepad_config_line_to_button[number],                 \
600   sizeof(gamepad_config_buttons) / sizeof(gamepad_config_buttons[0]),         \
601   gamepad_help[gamepad_config_map[                                            \
602    gamepad_config_line_to_button[number]]],                                   \
603   number,                                                                     \
604   STRING_SELECTION_OPTION                                                     \
605 }                                                                             \
606
607 #define analog_config_option(display_string, number)                          \
608 {                                                                             \
609   NULL,                                                                       \
610   menu_fix_gamepad_help,                                                      \
611   NULL,                                                                       \
612   display_string ": %s",                                                      \
613   gamepad_config_buttons,                                                     \
614   gamepad_config_map + number + 12,                                           \
615   sizeof(gamepad_config_buttons) / sizeof(gamepad_config_buttons[0]),         \
616   gamepad_help[gamepad_config_map[number + 12]],                              \
617   number + 2,                                                                 \
618   STRING_SELECTION_OPTION                                                     \
619 }                                                                             \
620
621 #define cheat_option(number)                                                  \
622 {                                                                             \
623   NULL,                                                                       \
624   NULL,                                                                       \
625   NULL,                                                                       \
626   cheat_format_str[number],                                                   \
627   enable_disable_options,                                                     \
628   &(cheats[number].cheat_active),                                             \
629   2,                                                                          \
630   "Activate/deactivate this cheat code.",                                     \
631   number,                                                                     \
632   STRING_SELECTION_OPTION                                                     \
633 }                                                                             \
634
635 #define action_option(action_function, passive_function, display_string,      \
636  help_string, line_number)                                                    \
637 {                                                                             \
638   action_function,                                                            \
639   passive_function,                                                           \
640   NULL,                                                                       \
641   display_string,                                                             \
642   NULL,                                                                       \
643   NULL,                                                                       \
644   0,                                                                          \
645   help_string,                                                                \
646   line_number,                                                                \
647   ACTION_OPTION                                                               \
648 }                                                                             \
649
650 #define submenu_option(sub_menu, display_string, help_string, line_number)    \
651 {                                                                             \
652   NULL,                                                                       \
653   NULL,                                                                       \
654   sub_menu,                                                                   \
655   display_string,                                                             \
656   NULL,                                                                       \
657   NULL,                                                                       \
658   sizeof(sub_menu) / sizeof(menu_option_type),                                \
659   help_string,                                                                \
660   line_number,                                                                \
661   SUBMENU_OPTION                                                              \
662 }                                                                             \
663
664 #define selection_option(passive_function, display_string, options,           \
665  option_ptr, num_options, help_string, line_number, type)                     \
666 {                                                                             \
667   NULL,                                                                       \
668   passive_function,                                                           \
669   NULL,                                                                       \
670   display_string,                                                             \
671   options,                                                                    \
672   option_ptr,                                                                 \
673   num_options,                                                                \
674   help_string,                                                                \
675   line_number,                                                                \
676   type                                                                        \
677 }                                                                             \
678
679 #define action_selection_option(action_function, passive_function,            \
680  display_string, options, option_ptr, num_options, help_string, line_number,  \
681  type)                                                                        \
682 {                                                                             \
683   action_function,                                                            \
684   passive_function,                                                           \
685   NULL,                                                                       \
686   display_string,                                                             \
687   options,                                                                    \
688   option_ptr,                                                                 \
689   num_options,                                                                \
690   help_string,                                                                \
691   line_number,                                                                \
692   type | ACTION_OPTION                                                        \
693 }                                                                             \
694
695
696 #define string_selection_option(passive_function, display_string, options,    \
697  option_ptr, num_options, help_string, line_number)                           \
698   selection_option(passive_function, display_string ": %s", options,          \
699    option_ptr, num_options, help_string, line_number, STRING_SELECTION_OPTION)\
700
701 #define numeric_selection_option(passive_function, display_string,            \
702  option_ptr, num_options, help_string, line_number)                           \
703   selection_option(passive_function, display_string ": %d", NULL, option_ptr, \
704    num_options, help_string, line_number, NUMBER_SELECTION_OPTION)            \
705
706 #define string_selection_action_option(action_function, passive_function,     \
707  display_string, options, option_ptr, num_options, help_string, line_number)  \
708   action_selection_option(action_function, passive_function,                  \
709    display_string ": %s",  options, option_ptr, num_options, help_string,     \
710    line_number, STRING_SELECTION_OPTION)                                      \
711
712 #define numeric_selection_action_option(action_function, passive_function,    \
713  display_string, option_ptr, num_options, help_string, line_number)           \
714   action_selection_option(action_function, passive_function,                  \
715    display_string ": %d",  NULL, option_ptr, num_options, help_string,        \
716    line_number, NUMBER_SELECTION_OPTION)                                      \
717
718 #define numeric_selection_action_hide_option(action_function,                 \
719  passive_function, display_string, option_ptr, num_options, help_string,      \
720  line_number)                                                                 \
721   action_selection_option(action_function, passive_function,                  \
722    display_string, NULL, option_ptr, num_options, help_string,                \
723    line_number, NUMBER_SELECTION_OPTION)                                      \
724
725
726 #define GAMEPAD_MENU_WIDTH 15
727
728 #ifdef PSP_BUILD
729
730 u32 gamepad_config_line_to_button[] =
731  { 8, 6, 7, 9, 1, 2, 3, 0, 4, 5, 11, 10 };
732
733 #endif
734
735 #ifdef GP2X_BUILD
736
737 u32 gamepad_config_line_to_button[] =
738  { 0, 2, 1, 3, 8, 9, 10, 11, 6, 7, 4, 5, 14, 15 };
739
740 #endif
741
742 #ifdef PND_BUILD
743
744 u32 gamepad_config_line_to_button[] =
745  { 0, 2, 1, 3, 8, 9, 10, 11, 6, 7, 4, 5, 12, 13, 14, 15 };
746
747 #endif
748
749 #ifdef RPI_BUILD
750
751 u32 gamepad_config_line_to_button[] =
752  { 0, 2, 1, 3, 8, 9, 10, 11, 6, 7, 4, 5, 12, 13, 14, 15 };
753
754 #endif
755
756 static const char *scale_options[] =
757 {
758 #ifdef PSP_BUILD
759   "unscaled 3:2", "scaled 3:2", "fullscreen 16:9"
760 #elif defined(WIZ_BUILD)
761   "unscaled 3:2", "scaled 3:2 (slower)",
762   "unscaled 3:2 (anti-tear)", "scaled 3:2 (anti-tear)"
763 #elif defined(POLLUX_BUILD)
764   "unscaled 3:2", "scaled 3:2 (slower)"
765 #elif defined(PND_BUILD)
766   "unscaled", "2x", "3x", "fullscreen"
767 #elif defined(GP2X_BUILD)
768   "unscaled 3:2", "scaled 3:2", "fullscreen", "scaled 3:2 (software)"
769 #elif defined(RPI_BUILD)
770   "fullscreen"
771 #else
772   "unscaled 3:2"
773 #endif
774 };
775
776 const char *filter2_options[] =
777 {
778   "none", "scale2x", "scale3x", "eagle2x"
779 };
780
781 #ifndef PSP_BUILD
782 static const char *audio_buffer_options[] =
783 {
784   "16 bytes", "32 bytes", "64 bytes",
785   "128 bytes", "256 bytes", "512 bytes", "1024 bytes", "2048 bytes",
786   "4096 bytes", "8192 bytes", "16284 bytes"
787 };
788 #else
789 const char *audio_buffer_options[] =
790 {
791   "3072 bytes", "4096 bytes", "5120 bytes", "6144 bytes", "7168 bytes",
792   "8192 bytes", "9216 bytes", "10240 bytes", "11264 bytes", "12288 bytes"
793 };
794 #endif
795
796
797 s32 load_game_config_file()
798 {
799   char game_config_filename[512];
800   u32 file_loaded = 0;
801   u32 i;
802   make_rpath(game_config_filename, sizeof(game_config_filename), ".cfg");
803
804   file_open(game_config_file, game_config_filename, read);
805
806   if(file_check_valid(game_config_file))
807   {
808     u32 file_size = file_length(game_config_filename, game_config_file);
809
810     // Sanity check: File size must be the right size
811     if(file_size == 56)
812     {
813       u32 file_options[file_size / 4];
814
815       file_read_array(game_config_file, file_options);
816       current_frameskip_type = file_options[0] % 3;
817       frameskip_value = file_options[1];
818       random_skip = file_options[2] % 2;
819       clock_speed = file_options[3];
820
821 #ifdef POLLUX_BUILD
822       if(clock_speed > 900)
823         clock_speed = 533;
824 #elif defined(GP2X_BUILD)
825       if(clock_speed >= 300)
826         clock_speed = 200;
827 #else
828       if(clock_speed > 333)
829         clock_speed = 333;
830 #endif
831
832       if(clock_speed < 33)
833         clock_speed = 33;
834
835       if(frameskip_value < 0)
836         frameskip_value = 0;
837
838       if(frameskip_value > 99)
839         frameskip_value = 99;
840
841       for(i = 0; i < 10; i++)
842       {
843         cheats[i].cheat_active = file_options[4 + i] % 2;
844         cheats[i].cheat_name[0] = 0;
845       }
846
847       file_close(game_config_file);
848       file_loaded = 1;
849     }
850   }
851
852   if(file_loaded)
853     return 0;
854
855 #ifdef RPI_BUILD
856   current_frameskip_type = manual_frameskip;
857   frameskip_value = 1;
858 #else
859   current_frameskip_type = auto_frameskip;
860   frameskip_value = 4;
861 #ifdef POLLUX_BUILD
862   frameskip_value = 1;
863 #endif
864 #endif
865   random_skip = 0;
866   clock_speed = default_clock_speed;
867
868   for(i = 0; i < 10; i++)
869   {
870     cheats[i].cheat_active = 0;
871     cheats[i].cheat_name[0] = 0;
872   }
873
874   return -1;
875 }
876
877 enum file_options {
878   fo_screen_scale = 0,
879   fo_screen_filter,
880   fo_global_enable_audio,
881   fo_audio_buffer_size,
882   fo_update_backup_flag,
883   fo_global_enable_analog,
884   fo_analog_sensitivity_level,
885   fo_screen_filter2,
886   fo_main_option_count,
887 };
888
889 #ifdef PC_BUILD
890 #define PLAT_BUTTON_COUNT 0
891 #endif
892 #define FILE_OPTION_COUNT (fo_main_option_count + PLAT_BUTTON_COUNT)
893
894 s32 load_config_file()
895 {
896   char config_path[512];
897
898   sprintf(config_path, "%s" PATH_SEPARATOR "%s", main_path, GPSP_CONFIG_FILENAME);
899
900   file_open(config_file, config_path, read);
901
902   if(file_check_valid(config_file))
903   {
904     u32 file_size = file_length(config_path, config_file);
905
906     // Sanity check: File size must be the right size
907     if(file_size == FILE_OPTION_COUNT * 4)
908     {
909       u32 file_options[file_size / 4];
910       file_read_array(config_file, file_options);
911
912       screen_scale = file_options[fo_screen_scale] %
913         (sizeof(scale_options) / sizeof(scale_options[0]));
914       screen_filter = file_options[fo_screen_filter] % 2;
915       global_enable_audio = file_options[fo_global_enable_audio] % 2;
916       screen_filter2 = file_options[fo_screen_filter2] %
917         (sizeof(filter2_options) / sizeof(filter2_options[0]));
918
919       audio_buffer_size_number = file_options[fo_audio_buffer_size] %
920         (sizeof(audio_buffer_options) / sizeof(audio_buffer_options[0]));
921
922       update_backup_flag = file_options[fo_update_backup_flag] % 2;
923       global_enable_analog = file_options[fo_global_enable_analog] % 2;
924       analog_sensitivity_level = file_options[fo_analog_sensitivity_level] % 8;
925
926 #ifdef PSP_BUILD
927     scePowerSetClockFrequency(clock_speed, clock_speed, clock_speed / 2);
928 #endif
929
930       // Sanity check: Make sure there's a MENU or FRAMESKIP
931       // key, if not assign to triangle
932
933 #ifndef PC_BUILD
934       u32 i;
935       s32 menu_button = -1;
936       for(i = 0; i < PLAT_BUTTON_COUNT; i++)
937       {
938         gamepad_config_map[i] = file_options[fo_main_option_count + i] %
939          (BUTTON_ID_NONE + 1);
940
941         if(gamepad_config_map[i] == BUTTON_ID_MENU)
942         {
943           menu_button = i;
944         }
945       }
946
947       if(menu_button == -1 && PLAT_MENU_BUTTON >= 0)
948       {
949         gamepad_config_map[PLAT_MENU_BUTTON] = BUTTON_ID_MENU;
950       }
951 #endif
952
953       file_close(config_file);
954     }
955
956     return 0;
957   }
958
959   return -1;
960 }
961
962 s32 save_game_config_file()
963 {
964   char game_config_filename[512];
965   u32 i;
966
967   make_rpath(game_config_filename, sizeof(game_config_filename), ".cfg");
968
969   file_open(game_config_file, game_config_filename, write);
970
971   if(file_check_valid(game_config_file))
972   {
973     u32 file_options[14];
974
975     file_options[0] = current_frameskip_type;
976     file_options[1] = frameskip_value;
977     file_options[2] = random_skip;
978     file_options[3] = clock_speed;
979
980     for(i = 0; i < 10; i++)
981     {
982       file_options[4 + i] = cheats[i].cheat_active;
983     }
984
985     file_write_array(game_config_file, file_options);
986
987     file_close(game_config_file);
988
989     return 0;
990   }
991
992   return -1;
993 }
994
995 s32 save_config_file()
996 {
997   char config_path[512];
998
999   sprintf(config_path, "%s" PATH_SEPARATOR "%s", main_path, GPSP_CONFIG_FILENAME);
1000
1001   file_open(config_file, config_path, write);
1002
1003   save_game_config_file();
1004
1005   if(file_check_valid(config_file))
1006   {
1007     u32 file_options[FILE_OPTION_COUNT];
1008
1009     file_options[fo_screen_scale] = screen_scale;
1010     file_options[fo_screen_filter] = screen_filter;
1011     file_options[fo_global_enable_audio] = global_enable_audio;
1012     file_options[fo_audio_buffer_size] = audio_buffer_size_number;
1013     file_options[fo_update_backup_flag] = update_backup_flag;
1014     file_options[fo_global_enable_analog] = global_enable_analog;
1015     file_options[fo_analog_sensitivity_level] = analog_sensitivity_level;
1016     file_options[fo_screen_filter2] = screen_filter2;
1017
1018 #ifndef PC_BUILD
1019     u32 i;
1020     for(i = 0; i < PLAT_BUTTON_COUNT; i++)
1021     {
1022       file_options[fo_main_option_count + i] = gamepad_config_map[i];
1023     }
1024 #endif
1025
1026     file_write_array(config_file, file_options);
1027
1028     file_close(config_file);
1029
1030     return 0;
1031   }
1032
1033   return -1;
1034 }
1035
1036 typedef enum
1037 {
1038   MAIN_MENU,
1039   GAMEPAD_MENU,
1040   SAVESTATE_MENU,
1041   FRAMESKIP_MENU,
1042   CHEAT_MENU
1043 } menu_enum;
1044
1045 u32 savestate_slot = 0;
1046
1047 void get_savestate_snapshot(char *savestate_filename)
1048 {
1049   u16 snapshot_buffer[240 * 160];
1050   char savestate_timestamp_string[80];
1051
1052   file_open(savestate_file, savestate_filename, read);
1053
1054   if(file_check_valid(savestate_file))
1055   {
1056     const char weekday_strings[7][11] =
1057     {
1058       "Sunday", "Monday", "Tuesday", "Wednesday",
1059       "Thursday", "Friday", "Saturday"
1060     };
1061     time_t savestate_time_flat;
1062     struct tm *current_time;
1063     file_read_array(savestate_file, snapshot_buffer);
1064     file_read_variable(savestate_file, savestate_time_flat);
1065
1066     file_close(savestate_file);
1067
1068     current_time = localtime(&savestate_time_flat);
1069     sprintf(savestate_timestamp_string,
1070      "%s  %02d/%02d/%04d  %02d:%02d:%02d                ",
1071      weekday_strings[current_time->tm_wday], current_time->tm_mon + 1,
1072      current_time->tm_mday, current_time->tm_year + 1900,
1073      current_time->tm_hour, current_time->tm_min, current_time->tm_sec);
1074
1075     savestate_timestamp_string[40] = 0;
1076     print_string(savestate_timestamp_string, COLOR_HELP_TEXT, COLOR_BG,
1077      10, 40);
1078   }
1079   else
1080   {
1081     memset(snapshot_buffer, 0, 240 * 160 * 2);
1082     print_string_ext("No savestate in this slot.",
1083      0xFFFF, 0x0000, 15, 75, snapshot_buffer, 240, 0, 0, FONT_HEIGHT);
1084     print_string("---------- --/--/---- --:--:--          ", COLOR_HELP_TEXT,
1085      COLOR_BG, 10, 40);
1086   }
1087
1088 #ifndef GP2X_BUILD
1089   blit_to_screen(snapshot_buffer, 240, 160, 230, 40);
1090 #endif
1091 }
1092
1093 void get_savestate_filename_noshot(u32 slot, char *name_buffer)
1094 {
1095   char savestate_ext[16];
1096
1097   sprintf(savestate_ext, "%d.svs", slot);
1098   make_rpath(name_buffer, 512, savestate_ext);
1099 }
1100
1101 void get_savestate_filename(u32 slot, char *name_buffer)
1102 {
1103   get_savestate_filename_noshot(slot, name_buffer);
1104   get_savestate_snapshot(name_buffer);
1105 }
1106
1107 #ifdef PSP_BUILD
1108   void _flush_cache()
1109   {
1110     invalidate_all_cache();
1111   }
1112 #endif
1113
1114 u32 menu(u16 *original_screen)
1115 {
1116   char print_buffer[81];
1117   u32 clock_speed_number;
1118   gui_action_type gui_action;
1119   u32 i;
1120   u32 repeat = 1;
1121   u32 return_value = 0;
1122   u32 first_load = 0;
1123   char current_savestate_filename[512];
1124   char line_buffer[80];
1125   char cheat_format_str[10][41];
1126
1127   menu_type *current_menu;
1128   menu_option_type *current_option;
1129   menu_option_type *display_option;
1130   u32 current_option_num;
1131
1132   auto void choose_menu();
1133   auto void clear_help();
1134
1135 #ifndef PC_BUILD
1136   static const char * const gamepad_help[] =
1137   {
1138     "Up button on GBA d-pad.",
1139     "Down button on GBA d-pad.",
1140     "Left button on GBA d-pad.",
1141     "Right button on GBA d-pad.",
1142     "A button on GBA.",
1143     "B button on GBA.",
1144     "Left shoulder button on GBA.",
1145     "Right shoulder button on GBA.",
1146     "Start button on GBA.",
1147     "Select button on GBA.",
1148     "Brings up the options menu.",
1149     "Toggles fastforward on/off.",
1150     "Loads the game state from the current slot.",
1151     "Saves the game state to the current slot.",
1152     "Rapidly press/release the A button on GBA.",
1153     "Rapidly press/release the B button on GBA.",
1154     "Rapidly press/release the L shoulder on GBA.",
1155     "Rapidly press/release the R shoulder on GBA.",
1156     "Increases the volume.",
1157     "Decreases the volume.",
1158     "Displays virtual/drawn frames per second.",
1159     "Does nothing."
1160   };
1161
1162   static const char *gamepad_config_buttons[] =
1163   {
1164     "UP",
1165     "DOWN",
1166     "LEFT",
1167     "RIGHT",
1168     "A",
1169     "B",
1170     "L",
1171     "R",
1172     "START",
1173     "SELECT",
1174     "MENU",
1175     "FASTFORWARD",
1176     "LOAD STATE",
1177     "SAVE STATE",
1178     "RAPIDFIRE A",
1179     "RAPIDFIRE B",
1180     "RAPIDFIRE L",
1181     "RAPIDFIRE R",
1182     "VOLUME UP",
1183     "VOLUME DOWN",
1184     "DISPLAY FPS",
1185     "NOTHING"
1186   };
1187 #endif
1188
1189   void menu_update_clock()
1190   {
1191     get_clock_speed_number();
1192     if (clock_speed_number < 0 || clock_speed_number >=
1193      sizeof(clock_speed_options) / sizeof(clock_speed_options[0]))
1194     {
1195       clock_speed = default_clock_speed;
1196       get_clock_speed_number();
1197     }
1198   }
1199
1200   void menu_exit()
1201   {
1202     if(!first_load)
1203      repeat = 0;
1204   }
1205
1206   void menu_quit()
1207   {
1208     menu_get_clock_speed();
1209     save_config_file();
1210     quit();
1211   }
1212
1213   void menu_load()
1214   {
1215     const char *file_ext[] = { ".gba", ".bin", ".zip", NULL };
1216     char load_filename[512];
1217     save_game_config_file();
1218     if(load_file(file_ext, load_filename) != -1)
1219     {
1220        if(load_gamepak(load_filename) == -1)
1221        {
1222          quit();
1223        }
1224        reset_gba();
1225        return_value = 1;
1226        repeat = 0;
1227        reg[CHANGED_PC_STATUS] = 1;
1228        menu_update_clock();
1229     }
1230   }
1231
1232   void menu_restart()
1233   {
1234     if(!first_load)
1235     {
1236       reset_gba();
1237       reg[CHANGED_PC_STATUS] = 1;
1238       return_value = 1;
1239       repeat = 0;
1240     }
1241   }
1242
1243   void menu_change_state()
1244   {
1245     get_savestate_filename(savestate_slot, current_savestate_filename);
1246   }
1247
1248   void menu_save_state()
1249   {
1250     if(!first_load)
1251     {
1252       get_savestate_filename_noshot(savestate_slot,
1253        current_savestate_filename);
1254       save_state(current_savestate_filename, original_screen);
1255     }
1256     menu_change_state();
1257   }
1258
1259   void menu_load_state()
1260   {
1261     if(!first_load)
1262     {
1263       load_state(current_savestate_filename);
1264       return_value = 1;
1265       repeat = 0;
1266     }
1267   }
1268
1269   void menu_load_state_file()
1270   {
1271     const char *file_ext[] = { ".svs", NULL };
1272     char load_filename[512];
1273     if(load_file(file_ext, load_filename) != -1)
1274     {
1275       load_state(load_filename);
1276       return_value = 1;
1277       repeat = 0;
1278     }
1279     else
1280     {
1281       choose_menu(current_menu);
1282     }
1283   }
1284
1285   void menu_fix_gamepad_help()
1286   {
1287 #ifndef PC_BUILD
1288     clear_help();
1289     current_option->help_string =
1290      gamepad_help[gamepad_config_map[
1291      gamepad_config_line_to_button[current_option_num]]];
1292 #endif
1293   }
1294
1295   void submenu_graphics_sound()
1296   {
1297
1298   }
1299
1300   void submenu_cheats_misc()
1301   {
1302
1303   }
1304
1305   void submenu_gamepad()
1306   {
1307
1308   }
1309
1310   void submenu_analog()
1311   {
1312
1313   }
1314
1315   void submenu_savestate()
1316   {
1317     print_string("Savestate options:", COLOR_ACTIVE_ITEM, COLOR_BG, 10, 70);
1318     menu_change_state();
1319   }
1320
1321   void submenu_main()
1322   {
1323     strncpy(print_buffer, gamepak_filename, 80);
1324     print_string(print_buffer, COLOR_ROM_INFO, COLOR_BG, 10, 10);
1325     sprintf(print_buffer, "%s  %s  %s", gamepak_title,
1326      gamepak_code, gamepak_maker);
1327     print_string(print_buffer, COLOR_ROM_INFO, COLOR_BG, 10, 20);
1328
1329     get_savestate_filename_noshot(savestate_slot,
1330      current_savestate_filename);
1331   }
1332
1333   const char *yes_no_options[] = { "no", "yes" };
1334   const char *enable_disable_options[] = { "disabled", "enabled" };
1335
1336   const char *frameskip_options[] = { "automatic", "manual", "off" };
1337   const char *frameskip_variation_options[] = { "uniform", "random" };
1338
1339   static const char *update_backup_options[] = { "Exit only", "Automatic" };
1340
1341   // Marker for help information, don't go past this mark (except \n)------*
1342   menu_option_type graphics_sound_options[] = 
1343  {
1344 #ifndef RPI_BUILD
1345     string_selection_option(NULL, "Display scaling", scale_options,
1346      (u32 *)(&screen_scale),
1347      sizeof(scale_options) / sizeof(scale_options[0]),
1348 #ifndef GP2X_BUILD
1349      "Determines how the GBA screen is resized in relation to the\n"
1350      "entire screen."
1351 #ifdef PSP_BUILD
1352      " Select unscaled 3:2 for GBA resolution, scaled 3:2 for GBA\n"
1353      "aspect ratio scaled to fill the height of the PSP screen, and\n"
1354      "fullscreen to fill the entire PSP screen."
1355 #endif
1356 #endif
1357      "", 2),
1358 #endif
1359
1360 #ifndef GP2X_BUILD
1361     string_selection_option(NULL, "Screen filtering", yes_no_options,
1362      (u32 *)(&screen_filter), 2,
1363      "Determines whether or not filtering should be used when\n"
1364      "scaling the screen. Selecting this will produce a more even and\n"
1365      "smooth image, at the cost of being blurry and having less vibrant\n"
1366      "colors.", 3),
1367 #endif
1368 #if defined (PND_BUILD)
1369     string_selection_option(NULL, "Scaling filter", filter2_options,
1370      (u32 *)(&screen_filter2),
1371      sizeof(filter2_options) / sizeof(filter2_options[0]),
1372      "Optional pixel art scaling filter", 4),
1373 #endif
1374     string_selection_option(NULL, "Frameskip type", frameskip_options,
1375      (u32 *)(&current_frameskip_type), 3,
1376 #ifndef GP2X_BUILD
1377      "Determines what kind of frameskipping to use.\n"
1378      "Frameskipping may improve emulation speed of many games.\n"
1379 #endif
1380      "Off: Do not skip any frames.\n"
1381      "Auto: Skip up to N frames (see next opt) as needed.\n"
1382      "Manual: Always render only 1 out of N + 1 frames."
1383      , 5),
1384     numeric_selection_option(NULL, "Frameskip value", &frameskip_value, 100,
1385 #ifndef GP2X_BUILD
1386      "For auto frameskip, determines the maximum number of frames that\n"
1387      "are allowed to be skipped consecutively.\n"
1388      "For manual frameskip, determines the number of frames that will\n"
1389      "always be skipped."
1390 #endif
1391      "", 6),
1392     string_selection_option(NULL, "Framskip variation",
1393      frameskip_variation_options, &random_skip, 2,
1394 #ifndef GP2X_BUILD
1395      "If objects in the game flicker at a regular rate certain manual\n"
1396      "frameskip values may cause them to normally disappear. Change this\n"
1397      "value to 'random' to avoid this. Do not use otherwise, as it tends\n"
1398      "to make the image quality worse, especially in high motion games."
1399 #endif
1400      "", 7),
1401     string_selection_option(NULL, "Audio output", yes_no_options,
1402      &global_enable_audio, 2,
1403      "Select 'no' to turn off all audio output. This will\n"
1404      "not result in a significant change in performance.", 9),
1405 #ifndef PSP_BUILD
1406     string_selection_option(NULL, "Audio buffer", audio_buffer_options,
1407              &audio_buffer_size_number, 11,
1408 #else
1409     string_selection_option(NULL, "Audio buffer", audio_buffer_options,
1410              &audio_buffer_size_number, 10,
1411 #endif
1412
1413 #ifdef PSP_BUILD
1414      "Set the size (in bytes) of the audio buffer. Larger values may result\n"
1415      "in slightly better performance at the cost of latency; the lowest\n"
1416      "value will give the most responsive audio.\n"
1417      "This option requires gpSP to be restarted before it will take effect.",
1418 #else
1419      "Set the size (in bytes) of the audio buffer.\n"
1420      "This option requires gpSP restart to take effect.\n"
1421      "Settable values may be limited by SDL implementation.",
1422 #endif
1423      10),
1424     submenu_option(NULL, "Back", "Return to the main menu.", 12)
1425   };
1426
1427   make_menu(graphics_sound, submenu_graphics_sound, NULL);
1428
1429   menu_option_type cheats_misc_options[] =
1430   {
1431     cheat_option(0),
1432     cheat_option(1),
1433     cheat_option(2),
1434     cheat_option(3),
1435     cheat_option(4),
1436     cheat_option(5),
1437     cheat_option(6),
1438     cheat_option(7),
1439     cheat_option(8),
1440     cheat_option(9),
1441 #if defined(PSP_BUILD) || defined(GP2X_BUILD)
1442     string_selection_option(NULL, "Clock speed",
1443      clock_speed_options, &clock_speed_number,
1444      sizeof(clock_speed_options) / sizeof(clock_speed_options[0]),
1445      "Change the clock speed of the device. Higher clock\n"
1446      "speed will yield better performance, but will drain\n"
1447      "battery life further.", 11),
1448 #endif
1449     string_selection_option(NULL, "Update backup",
1450      update_backup_options, &update_backup_flag, 2,
1451 #ifdef GP2X_BUILD
1452      "Determines when in-game save files should be\n"
1453      "written back to SD card."
1454 #else
1455      "Determines when in-game save files should be written back to\n"
1456      "card. If set to 'automatic' writebacks will occur shortly after\n"
1457      "the game's backup is altered. On 'exit only' it will only be\n"
1458      "written back when you exit from this menu.\n"
1459 #ifdef PSP
1460      "(NOT from using the home button), use the latter with extreme care."
1461 #endif
1462 #endif
1463      "", 12),
1464     submenu_option(NULL, "Back", "Return to the main menu.", 14)
1465   };
1466
1467   make_menu(cheats_misc, submenu_cheats_misc, NULL);
1468
1469   menu_option_type savestate_options[] =
1470   {
1471     numeric_selection_action_hide_option(menu_load_state, menu_change_state,
1472      "Load savestate from current slot", &savestate_slot, 10,
1473      "Select to load the game state from the current slot\n"
1474      "for this game.\n"
1475      "Press left + right to change the current slot.", 6),
1476     numeric_selection_action_hide_option(menu_save_state, menu_change_state,
1477      "Save savestate to current slot", &savestate_slot, 10,
1478      "Select to save the game state to the current slot\n"
1479      "for this game.\n"
1480      "Press left + right to change the current slot.", 7),
1481     numeric_selection_action_hide_option(menu_load_state_file,
1482       menu_change_state,
1483      "Load savestate from file", &savestate_slot, 10,
1484      "Restore gameplay from a savestate file.\n"
1485      "Note: The same file used to save the state must be\n"
1486      "present.\n", 9),
1487     numeric_selection_option(menu_change_state,
1488      "Current savestate slot", &savestate_slot, 10,
1489      "Change the current savestate slot.\n", 11),
1490     submenu_option(NULL, "Back", "Return to the main menu.", 13)
1491   };
1492
1493   make_menu(savestate, submenu_savestate, NULL);
1494
1495 #ifdef PSP_BUILD
1496
1497   menu_option_type gamepad_config_options[] =
1498   {
1499     gamepad_config_option("D-pad up     ", 0),
1500     gamepad_config_option("D-pad down   ", 1),
1501     gamepad_config_option("D-pad left   ", 2),
1502     gamepad_config_option("D-pad right  ", 3),
1503     gamepad_config_option("Circle       ", 4),
1504     gamepad_config_option("Cross        ", 5),
1505     gamepad_config_option("Square       ", 6),
1506     gamepad_config_option("Triangle     ", 7),
1507     gamepad_config_option("Left Trigger ", 8),
1508     gamepad_config_option("Right Trigger", 9),
1509     gamepad_config_option("Start        ", 10),
1510     gamepad_config_option("Select       ", 11),
1511     submenu_option(NULL, "Back", "Return to the main menu.", 13)
1512   };
1513
1514
1515   menu_option_type analog_config_options[] =
1516   {
1517     analog_config_option("Analog up   ", 0),
1518     analog_config_option("Analog down ", 1),
1519     analog_config_option("Analog left ", 2),
1520     analog_config_option("Analog right", 3),
1521     string_selection_option(NULL, "Enable analog", yes_no_options,
1522      &global_enable_analog, 2,
1523      "Select 'no' to block analog input entirely.", 7),
1524     numeric_selection_option(NULL, "Analog sensitivity",
1525      &analog_sensitivity_level, 10,
1526      "Determine sensitivity/responsiveness of the analog input.\n"
1527      "Lower numbers are less sensitive.", 8),
1528     submenu_option(NULL, "Back", "Return to the main menu.", 11)
1529   };
1530
1531 #endif
1532
1533 #if defined(GP2X_BUILD) || defined(PND_BUILD)
1534
1535   menu_option_type gamepad_config_options[] =
1536   {
1537     gamepad_config_option("D-pad up     ", 0),
1538     gamepad_config_option("D-pad down   ", 1),
1539     gamepad_config_option("D-pad left   ", 2),
1540     gamepad_config_option("D-pad right  ", 3),
1541     gamepad_config_option("A            ", 4),
1542     gamepad_config_option("B            ", 5),
1543     gamepad_config_option("X            ", 6),
1544     gamepad_config_option("Y            ", 7),
1545     gamepad_config_option("Left Trigger ", 8),
1546     gamepad_config_option("Right Trigger", 9),
1547 #ifdef WIZ_BUILD
1548     gamepad_config_option("Menu         ", 10),
1549     gamepad_config_option("Select       ", 11),
1550 #elif defined(POLLUX_BUILD)
1551     gamepad_config_option("I            ", 10),
1552     gamepad_config_option("II           ", 11),
1553     gamepad_config_option("Push         ", 12),
1554     gamepad_config_option("Home         ", 13),
1555 #elif defined(PND_BUILD)
1556     gamepad_config_option("Start        ", 10),
1557     gamepad_config_option("Select       ", 11),
1558     gamepad_config_option("1            ", 12),
1559     gamepad_config_option("2            ", 13),
1560     gamepad_config_option("3            ", 14),
1561     gamepad_config_option("4            ", 15),
1562 #else // GP2X
1563     gamepad_config_option("Start        ", 10),
1564     gamepad_config_option("Select       ", 11),
1565     gamepad_config_option("Stick Push   ", 12),
1566 #endif
1567 #ifdef PND_BUILD
1568     submenu_option(NULL, "Back", "Return to the main menu.", 16)
1569 #else
1570     submenu_option(NULL, "Back", "Return to the main menu.", 14)
1571 #endif
1572   };
1573
1574
1575   menu_option_type analog_config_options[] =
1576   {
1577 #if defined(POLLUX_BUILD)
1578     numeric_selection_option(NULL, "Analog sensitivity",
1579      &analog_sensitivity_level, 10,
1580      "Determine sensitivity/responsiveness of the analog input.\n"
1581      "Lower numbers are less sensitive.", 8),
1582 #endif
1583     submenu_option(NULL, "Back", "Return to the main menu.", 11)
1584   };
1585
1586 #endif
1587
1588 #if defined(PC_BUILD) || defined(RPI_BUILD)
1589
1590   menu_option_type gamepad_config_options[] =
1591   {
1592     submenu_option(NULL, "Back", "Return to the main menu.", 13)
1593   };
1594
1595   menu_option_type analog_config_options[] =
1596   {
1597     submenu_option(NULL, "Back", "Return to the main menu.", 11)
1598   };
1599
1600 #endif
1601
1602   make_menu(gamepad_config, submenu_gamepad, NULL);
1603   make_menu(analog_config, submenu_analog, NULL);
1604
1605   menu_option_type main_options[] =
1606   {
1607     submenu_option(&graphics_sound_menu, "Graphics and Sound options",
1608      "Select to set display parameters and frameskip\n"
1609      "behavior, audio on/off, buffer size, and filtering.", 0),
1610     numeric_selection_action_option(menu_load_state, NULL,
1611      "Load state from slot", &savestate_slot, 10,
1612      "Select to load the game state from the current slot\n"
1613      "for this game, if it exists.\n"
1614      "Press left + right to change the current slot.", 2),
1615     numeric_selection_action_option(menu_save_state, NULL,
1616      "Save state to slot", &savestate_slot, 10,
1617      "Select to save the game state to the current slot\n"
1618      "for this game. See the extended menu for more info.\n"
1619      "Press left + right to change the current slot.", 3),
1620     submenu_option(&savestate_menu, "Savestate options",
1621      "Select to enter a menu for loading, saving, and\n"
1622      "viewing the currently active savestate for this game\n"
1623      "(or to load a savestate file from another game)", 4),
1624     submenu_option(&gamepad_config_menu, "Configure gamepad input",
1625      "Select to change the in-game behavior of buttons\n"
1626      "and d-pad.", 6),
1627 #ifndef WIZ_BUILD
1628     submenu_option(&analog_config_menu, "Configure analog input",
1629      "Select to change the in-game behavior of the analog nub.", 7),
1630 #endif
1631     submenu_option(&cheats_misc_menu, "Cheats and Miscellaneous options",
1632      "Select to manage cheats, set backup behavior,\n"
1633      "and set device clock speed.", 9),
1634     action_option(menu_load, NULL, "Load new game",
1635      "Select to load a new game\n"
1636      "(will exit a game if currently playing).", 11),
1637     action_option(menu_restart, NULL, "Restart game",
1638      "Select to reset the GBA with the current game\n"
1639      "loaded.", 12),
1640     action_option(menu_exit, NULL, "Return to game",
1641      "Select to exit this menu and resume gameplay.", 13),
1642     action_option(menu_quit, NULL, "Exit gpSP",
1643      "Select to exit gpSP and return to the menu.", 15)
1644   };
1645
1646   make_menu(main, submenu_main, NULL);
1647
1648   void choose_menu(menu_type *new_menu)
1649   {
1650     if(new_menu == NULL)
1651       new_menu = &main_menu;
1652
1653     clear_screen(COLOR_BG);
1654
1655 #ifndef GP2X_BUILD
1656     blit_to_screen(original_screen, 240, 160, 230, 40);
1657 #endif
1658
1659     current_menu = new_menu;
1660     current_option = new_menu->options;
1661     current_option_num = 0;
1662     if(current_menu->init_function)
1663      current_menu->init_function();
1664   }
1665
1666   void clear_help()
1667   {
1668     for(i = 0; i < 6; i++)
1669     {
1670       print_string_pad(" ", COLOR_BG, COLOR_BG, 8, 210 + (i * 10), 70);
1671     }
1672   }
1673
1674   menu_update_clock();
1675   video_resolution_large();
1676
1677 #ifndef GP2X_BUILD
1678   SDL_LockMutex(sound_mutex);
1679 #endif
1680   SDL_PauseAudio(1);
1681
1682 #ifndef GP2X_BUILD
1683   SDL_UnlockMutex(sound_mutex);
1684 #endif
1685
1686   if(gamepak_filename[0] == 0)
1687   {
1688     first_load = 1;
1689     memset(original_screen, 0x00, 240 * 160 * 2);
1690     print_string_ext("No game loaded yet.", 0xFFFF, 0x0000,
1691      60, 75,original_screen, 240, 0, 0, FONT_HEIGHT);
1692   }
1693
1694   choose_menu(&main_menu);
1695
1696   for(i = 0; i < 10; i++)
1697   {
1698     if(i >= num_cheats)
1699     {
1700       sprintf(cheat_format_str[i], "cheat %d (none loaded)", i);
1701     }
1702     else
1703     {
1704       sprintf(cheat_format_str[i], "cheat %d (%s): %%s", i,
1705        cheats[i].cheat_name);
1706     }
1707   }
1708
1709   current_menu->init_function();
1710
1711   while(repeat)
1712   {
1713     display_option = current_menu->options;
1714
1715     for(i = 0; i < current_menu->num_options; i++, display_option++)
1716     {
1717       if(display_option->option_type & NUMBER_SELECTION_OPTION)
1718       {
1719         sprintf(line_buffer, display_option->display_string,
1720          *(display_option->current_option));
1721       }
1722       else
1723
1724       if(display_option->option_type & STRING_SELECTION_OPTION)
1725       {
1726         sprintf(line_buffer, display_option->display_string,
1727          ((u32 *)display_option->options)[*(display_option->current_option)]);
1728       }
1729       else
1730       {
1731         strcpy(line_buffer, display_option->display_string);
1732       }
1733
1734       if(display_option == current_option)
1735       {
1736         print_string_pad(line_buffer, COLOR_ACTIVE_ITEM, COLOR_BG, 6,
1737          (display_option->line_number * 10) + 40, 36);
1738       }
1739       else
1740       {
1741         print_string_pad(line_buffer, COLOR_INACTIVE_ITEM, COLOR_BG, 6,
1742          (display_option->line_number * 10) + 40, 36);
1743       }
1744     }
1745
1746     print_string(current_option->help_string, COLOR_HELP_TEXT,
1747      COLOR_BG, 8, 210);
1748
1749     flip_screen();
1750
1751     gui_action = get_gui_input();
1752
1753     switch(gui_action)
1754     {
1755       case CURSOR_DOWN:
1756         current_option_num = (current_option_num + 1) %
1757           current_menu->num_options;
1758
1759         current_option = current_menu->options + current_option_num;
1760         clear_help();
1761         break;
1762
1763       case CURSOR_UP:
1764         if(current_option_num)
1765           current_option_num--;
1766         else
1767           current_option_num = current_menu->num_options - 1;
1768
1769         current_option = current_menu->options + current_option_num;
1770         clear_help();
1771         break;
1772
1773       case CURSOR_RIGHT:
1774         if(current_option->option_type & (NUMBER_SELECTION_OPTION |
1775          STRING_SELECTION_OPTION))
1776         {
1777           *(current_option->current_option) =
1778            (*current_option->current_option + 1) %
1779            current_option->num_options;
1780
1781           if(current_option->passive_function)
1782             current_option->passive_function();
1783         }
1784         break;
1785
1786       case CURSOR_LEFT:
1787         if(current_option->option_type & (NUMBER_SELECTION_OPTION |
1788          STRING_SELECTION_OPTION))
1789         {
1790           u32 current_option_val = *(current_option->current_option);
1791
1792           if(current_option_val)
1793             current_option_val--;
1794           else
1795             current_option_val = current_option->num_options - 1;
1796
1797           *(current_option->current_option) = current_option_val;
1798
1799           if(current_option->passive_function)
1800             current_option->passive_function();
1801         }
1802         break;
1803
1804       case CURSOR_EXIT:
1805         if(current_menu == &main_menu)
1806           menu_exit();
1807
1808         choose_menu(&main_menu);
1809         break;
1810
1811       case CURSOR_SELECT:
1812         if(current_option->option_type & ACTION_OPTION)
1813           current_option->action_function();
1814
1815         if(current_option->option_type & SUBMENU_OPTION)
1816           choose_menu(current_option->sub_menu);
1817
1818         if(current_menu == &main_menu)
1819            choose_menu(&main_menu);
1820
1821         break;
1822
1823       default:
1824         break;
1825     }
1826   }
1827
1828   set_gba_resolution(screen_scale);
1829   video_resolution_small();
1830   menu_get_clock_speed();
1831   set_clock_speed();
1832
1833   SDL_PauseAudio(0);
1834   num_skipped_frames = 100;
1835
1836   return return_value;
1837 }