fix crash when specifying --source on command line
[rofl0r-gnuboy.git] / menu.c
blob7b8d271163e03abbaa1255bb3c799135d8d8303d
1 #include <dirent.h>
2 #include <string.h>
3 #include <sys/stat.h>
4 #include <stdio.h>
6 #include "menu.h"
7 #include "sys.h"
8 #include "input.h"
9 #include "rc.h"
10 #include "rckeys.h"
11 #include "emu.h"
12 #include "loader.h"
14 #include "ezmenu.h"
15 #include "font5x7.h"
16 #include "fb.h"
17 #include "lcd.h"
19 #define FONTW 5
20 #define FONTH 7
21 #define FONTMAX 127
23 static char *romdir;
24 static struct ezmenu ezm;
25 static enum menu_page currpage;
26 static unsigned char screen[160*144];
27 static char statusline[64];
30 void menu_init(void) {
31 ezmenu_init(&ezm, 160, 144, FONTW, FONTH);
32 ezm.wraparound = 1;
35 static int allowed_ext(char *fn) {
36 static const char *exttab[] = {".gb", ".gbc", ".xz", ".gz", ".zip", 0};
37 char *e = strrchr(fn, '.');
38 if(!e) return 0;
39 int i;
40 for(i=0; exttab[i]; ++i)
41 if(!strcmp(exttab[i], e)) return 1;
42 return 0;
45 static int strlistcmp(const void* a, const void *b) {
46 return strcmp(*(char* const*) a, *(char* const*) b);
49 static int strendswith(char *s, char *end) {
50 size_t ls = strlen(s), le = strlen(end);
51 return ls >= le && !strcmp(s + ls - le, end);
54 /* this is defined this way so a couple 0 bytes can be added that can
55 be overwritten with the mapped key name, if desired */
56 static char* controller_menu_items[] = {
57 (char[]){'+', 'a', 0},
58 (char[]){'+', 'b', 0},
59 (char[]){'+', 's', 'e', 'l', 'e', 'c', 't', 0},
60 (char[]){'+', 's', 't', 'a', 'r', 't', 0},
61 (char[]){'m', 'e', 'n', 'u', 0},
62 (char[]){'b', 'a', 'c', 'k', 0},
65 void menu_initpage(enum menu_page page) {
66 static const char* loaderr_menu_items[] = {"back"};
67 static const char* main_menu_items[] = {
68 "continue",
69 "select rom",
70 "load state",
71 "save state",
72 "controller config",
73 "reset",
74 "quit",
76 static const char* state_menu_items[] = {
77 "state 0", "state 1", "state 2", "state 3", "state 4",
78 "state 5", "state 6", "state 7", "state 8", "state 9",
79 "back",
81 DIR *dir;
82 struct dirent* file;
83 char **dirlist = 0;
84 unsigned dirlistcnt = 0, i;
85 if(currpage == mp_romsel) {
86 for(i=0; i<ezm.linecount; ++i)
87 free(ezm.lines[i]);
88 free(ezm.lines);
90 switch(page) {
91 case mp_savestate:
92 case mp_loadstate:
93 ezmenu_setheader(&ezm, page == mp_savestate ? "Save state" : "Load state");
94 ezmenu_setlines(&ezm, (void*)state_menu_items, sizeof(state_menu_items)/sizeof(main_menu_items[0]));
95 ezmenu_setfooter(&ezm, " ");
96 break;
97 loaderr:
98 case mp_loaderr:
99 ezmenu_setheader(&ezm, loader_get_error());
100 ezmenu_setlines(&ezm, (void*)loaderr_menu_items, 1);
101 break;
102 case mp_main:
103 ezmenu_setheader(&ezm, "GNUBOY MAIN MENU");
104 ezmenu_setlines(&ezm, (void*)main_menu_items, sizeof(main_menu_items)/sizeof(main_menu_items[0]));
105 ezmenu_setfooter(&ezm, " ");
106 break;
107 case mp_controller:
108 ezmenu_setheader(&ezm, "Controller config");
109 ezmenu_setlines(&ezm, controller_menu_items, sizeof(controller_menu_items)/sizeof(controller_menu_items[0]));
110 ezmenu_setfooter(&ezm, " ");
111 break;
112 case mp_romsel:
113 dir = opendir(romdir);
114 if(!dir) {
115 loader_set_error("failed to open directory");
116 if(strendswith(romdir, "/.."))
117 romdir[strlen(romdir)-3] = 0;
118 page = mp_loaderr;
119 goto loaderr;
121 ezmenu_setheader(&ezm, "GNUBOY ROM Selection");
122 ezmenu_setfooter(&ezm, " ");
123 dirlist = malloc(sizeof(char*));
124 dirlist[0] = strdup("..");
125 dirlistcnt = 1;
126 while((file = readdir(dir))) {
127 if(file->d_name[0] == '.') continue;
128 if (!allowed_ext(file->d_name)) {
129 char path[1024];
130 struct stat st;
131 snprintf(path, sizeof path, "%s/%s", romdir, file->d_name);
132 if(!stat(path, &st) && S_ISDIR(st.st_mode));
133 else continue;
135 dirlist = realloc(dirlist, sizeof(char*)*(++dirlistcnt));
136 dirlist[dirlistcnt-1] = strdup(file->d_name);
138 closedir(dir);
139 qsort(dirlist+1, dirlistcnt-1, sizeof(char*), strlistcmp);
140 ezmenu_setlines(&ezm, dirlist, dirlistcnt);
141 break;
143 currpage = page;
146 static void font_blit(unsigned char* screen, int dx, int dy, int ch, int hl) {
147 unsigned char* dest = screen + dy*160 + dx;
148 unsigned const char* font = font5x7 + (ch>FONTMAX?0:ch)*FONTH*FONTW;
149 int x,y;
150 for(y = 0; y < FONTH; ++y, dest += 160)
151 for(x = 0; x < FONTW; ++x)
152 dest[x] = hl&&!font[y*FONTW+x]?2:font[y*FONTW+x];
155 struct palbkup {
156 char pal1[3];
157 short pal2[3];
158 int pal4[3];
161 static void bkup_pal(struct palbkup *bk) {
162 bk->pal1[0] = scan.pal1[0];
163 bk->pal1[1] = scan.pal1[1];
164 bk->pal1[2] = scan.pal1[2];
165 bk->pal2[0] = scan.pal2[0];
166 bk->pal2[1] = scan.pal2[1];
167 bk->pal2[2] = scan.pal2[2];
168 bk->pal4[0] = scan.pal4[0];
169 bk->pal4[1] = scan.pal4[1];
170 bk->pal4[2] = scan.pal4[2];
173 static void restore_pal(struct palbkup *bk) {
174 scan.pal1[0] = bk->pal1[0];
175 scan.pal1[1] = bk->pal1[1];
176 scan.pal1[2] = bk->pal1[2];
177 scan.pal2[0] = bk->pal2[0];
178 scan.pal2[1] = bk->pal2[1];
179 scan.pal2[2] = bk->pal2[2];
180 scan.pal4[0] = bk->pal4[0];
181 scan.pal4[1] = bk->pal4[1];
182 scan.pal4[2] = bk->pal4[2];
185 static void menu_paint(void) {
186 struct palbkup bk;
187 /* since we use gb's lcd routines to draw to vram, we have to backup
188 previous palette entries */
189 bkup_pal(&bk);
190 scan.pal1[0] = 0;
191 scan.pal1[1] = 0xff;
192 scan.pal1[2] = 0x66;
193 scan.pal2[0] = 0;
194 scan.pal2[1] = 0xffff;
195 scan.pal2[2] = 0x6666;
196 scan.pal4[0] = 0;
197 scan.pal4[1] = 0xffffffff; // alpha left or right ?
198 scan.pal4[2] = 0x66666666;
200 int x,y,l;
201 for(y = 0; y < ezm.h; ++y) {
202 l=strlen(ezm.vislines[y]);
203 for(x = 0; x < ezm.w; ++x)
204 font_blit(screen, x*FONTW, y*FONTH, x>=l?' ':ezm.vislines[y][x], y==ezm.vissel);
207 vid_begin();
208 lcd_begin();
210 for(y = 0; y < 144; ++y) {
211 memcpy(scan.buf, screen+160*y, 160);
212 lcd_linetovram();
215 vid_end();
216 restore_pal(&bk);
219 static int menu_getevent(int *st) {
220 event_t ev;
221 int polled = 0;
222 while (1) {
223 if(!ev_getevent(&ev)) {
224 if(polled) break;
225 ev_poll(1);
226 polled = 1;
227 continue;
230 if (ev.type != EV_PRESS && ev.type != EV_RELEASE)
231 continue;
232 *st = (ev.type != EV_RELEASE);
233 return ev.code;
235 return 0;
239 enum menu_key {
240 mk_ignore,
241 mk_down,
242 mk_up,
243 mk_ok,
244 mk_cancel,
247 static enum menu_key menu_translate_key(int k) {
248 char *bind;
249 switch(k) {
250 case K_UP:
251 case K_JOYUP:
252 return mk_up;
253 case K_DOWN:
254 case K_JOYDOWN:
255 return mk_down;
256 case K_ENTER:
257 return mk_ok;
258 case 0:
259 return mk_ignore;
260 default:
261 bind = rc_getkeybind(k);
262 if(!bind || (bind[0] != '+' && bind[0] != '-'))
263 return mk_ignore;
264 ++bind;
265 if(!strcmp(bind, "a")) return mk_ok;
266 if(!strcmp(bind, "b")) return mk_cancel;
267 if(!strcmp(bind, "start")) return mk_ok;
268 if(!strcmp(bind, "up")) return mk_up;
269 if(!strcmp(bind, "down")) return mk_down;
270 return mk_ignore;
274 void menu_enter(void) {
275 entry:;
276 ezmenu_update(&ezm);
277 menu_paint();
278 while(1) {
279 int st, k = menu_getevent(&st);
280 if (!k || !st) goto next;
281 switch(menu_translate_key(k)) {
282 case mk_up:
283 ezmenu_userinput(&ezm, EZM_UP);
284 menu_paint();
285 break;
286 case mk_down:
287 ezmenu_userinput(&ezm, EZM_DOWN);
288 menu_paint();
289 break;
290 case mk_cancel:
291 if(currpage == mp_main) break;
292 menu_initpage(mp_main);
293 goto entry;
294 case mk_ok:
295 if(currpage == mp_romsel) {
296 char rd[1024];
297 struct stat st;
298 snprintf(rd, sizeof rd, "%s/%s", romdir, ezm.vislines[ezm.vissel]);
299 if(!stat(rd, &st) && S_ISDIR(st.st_mode)) {
300 free(romdir);
301 romdir = strdup(rd);
302 menu_initpage(mp_romsel);
303 goto entry;
304 } else {
305 loader_unload();
306 if(load_rom_and_rc(rd)) {
307 menu_initpage(mp_loaderr);
308 goto entry;
310 goto out;
312 } else if (currpage == mp_main) {
313 if(!strcmp(ezm.vislines[ezm.vissel], "continue")) {
314 if(emu_paused()) goto out;
316 else if(!strcmp(ezm.vislines[ezm.vissel], "reset")) {
317 if(emu_paused()) {
318 emu_reset();
319 goto out;
322 else if(!strcmp(ezm.vislines[ezm.vissel], "select rom")) {
323 menu_initpage(mp_romsel);
324 goto entry;
326 else if(!strcmp(ezm.vislines[ezm.vissel], "controller config")) {
327 menu_initpage(mp_controller);
328 goto entry;
330 else if(!strcmp(ezm.vislines[ezm.vissel], "load state")) {
331 menu_initpage(mp_loadstate);
332 goto entry;
334 else if(!strcmp(ezm.vislines[ezm.vissel], "save state")) {
335 menu_initpage(mp_savestate);
336 goto entry;
338 else if(!strcmp(ezm.vislines[ezm.vissel], "quit")) {
339 loader_unload();
340 exit(0);
342 } else if (currpage == mp_loaderr) {
343 menu_initpage(mp_romsel);
344 goto entry;
345 } else if (currpage == mp_controller) {
346 if(!strcmp(ezm.vislines[ezm.vissel], "back")) {
347 menu_initpage(mp_main);
348 goto entry;
350 ezmenu_setfooter(&ezm, "press a button to override");
351 ezmenu_update(&ezm);
352 menu_paint();
353 while((k = menu_getevent(&st)) == 0 || !st);
354 rc_unbindkey(k_keyname(k));
355 rc_bindkey(k_keyname(k), ezm.vislines[ezm.vissel]);
356 snprintf(statusline, sizeof statusline, "key assigned: %s", k_keyname(k));
357 ezmenu_setfooter(&ezm, statusline);
358 ezmenu_update(&ezm);
359 menu_paint();
360 } else if (currpage == mp_savestate || currpage == mp_loadstate) {
361 if(!strcmp(ezm.vislines[ezm.vissel], "back")) {
362 menu_initpage(mp_main);
363 goto entry;
365 if(!emu_paused()) break;
366 int n = atoi(ezm.vislines[ezm.vissel]+6);
367 if(currpage == mp_savestate) state_save(n);
368 else state_load(n);
369 goto out;
371 break;
372 default:
373 next:;
374 sys_sleep(300);
377 out:; return;
380 rcvar_t menu_exports[] =
382 RCV_STRING("romdir", &romdir, "rom directory"),
383 RCV_END