// (c) Copyright 2006,2007 notaz, All rights reserved. // Free for non-commercial use. // For commercial use, separate licencing terms must be obtained. #include #include #include #include #include "menu.h" #include "fonts.h" #include "readpng.h" #include "lprintf.h" #include "common.h" #include "input.h" #include "emu.h" #include "plat.h" #include char menuErrorMsg[64] = { 0, }; // PicoPad[] format: MXYZ SACB RLDU me_bind_action me_ctrl_actions[15] = { { "UP ", 0x0001 }, { "DOWN ", 0x0002 }, { "LEFT ", 0x0004 }, { "RIGHT ", 0x0008 }, { "A ", 0x0040 }, { "B ", 0x0010 }, { "C ", 0x0020 }, { "A turbo", 0x4000 }, { "B turbo", 0x1000 }, { "C turbo", 0x2000 }, { "START ", 0x0080 }, { "MODE ", 0x0800 }, { "X ", 0x0400 }, { "Y ", 0x0200 }, { "Z ", 0x0100 } }; #ifndef UIQ3 static unsigned char menu_font_data[10240]; static int menu_text_color = 0xffff; // default to white static int menu_sel_color = -1; // disabled // draws text to current bbp16 screen static void text_out16_(int x, int y, const char *text, int color) { int i, l, u, tr, tg, tb, len; unsigned short *dest = (unsigned short *)SCREEN_BUFFER + x + y*SCREEN_WIDTH; tr = (color & 0xf800) >> 8; tg = (color & 0x07e0) >> 3; tb = (color & 0x001f) << 3; if (text == (void *)1) { // selector symbol text = ""; len = 1; } else len = strlen(text); for (i = 0; i < len; i++) { unsigned char *src = menu_font_data + (unsigned int)text[i]*4*10; unsigned short *dst = dest; for (l = 0; l < 10; l++, dst += SCREEN_WIDTH-8) { for (u = 8/2; u > 0; u--, src++) { int c, r, g, b; c = *src >> 4; r = (*dst & 0xf800) >> 8; g = (*dst & 0x07e0) >> 3; b = (*dst & 0x001f) << 3; r = (c^0xf)*r/15 + c*tr/15; g = (c^0xf)*g/15 + c*tg/15; b = (c^0xf)*b/15 + c*tb/15; *dst++ = ((r<<8)&0xf800) | ((g<<3)&0x07e0) | (b>>3); c = *src & 0xf; r = (*dst & 0xf800) >> 8; g = (*dst & 0x07e0) >> 3; b = (*dst & 0x001f) << 3; r = (c^0xf)*r/15 + c*tr/15; g = (c^0xf)*g/15 + c*tg/15; b = (c^0xf)*b/15 + c*tb/15; *dst++ = ((r<<8)&0xf800) | ((g<<3)&0x07e0) | (b>>3); } } dest += 8; } } void text_out16(int x, int y, const char *texto, ...) { va_list args; char buffer[256]; int maxw = (SCREEN_WIDTH - x) / 8; va_start(args, texto); vsnprintf(buffer, sizeof(buffer), texto, args); va_end(args); if (maxw > 255) maxw = 255; buffer[maxw] = 0; text_out16_(x,y,buffer,menu_text_color); } void smalltext_out16(int x, int y, const char *texto, int color) { int i; unsigned char *src; unsigned short *dst; for (i = 0;; i++, x += 6) { unsigned char c = (unsigned char) texto[i]; int h = 8; if (!c) break; src = fontdata6x8[c]; dst = (unsigned short *)SCREEN_BUFFER + x + y*SCREEN_WIDTH; while (h--) { int w = 0x20; while (w) { if( *src & w ) *dst = color; dst++; w>>=1; } src++; dst += SCREEN_WIDTH-6; } } } void smalltext_out16_lim(int x, int y, const char *texto, int color, int max) { char buffer[SCREEN_WIDTH/6+1]; strncpy(buffer, texto, SCREEN_WIDTH/6); if (max > SCREEN_WIDTH/6) max = SCREEN_WIDTH/6; if (max < 0) max = 0; buffer[max] = 0; smalltext_out16(x, y, buffer, color); } void menu_draw_selection(int x, int y, int w) { int i, h; unsigned short *dst, *dest; text_out16_(x, y, (void *)1, (menu_sel_color < 0) ? menu_text_color : menu_sel_color); if (menu_sel_color < 0) return; // no selection hilight if (y > 0) y--; dest = (unsigned short *)SCREEN_BUFFER + x + y*SCREEN_WIDTH + 14; for (h = 11; h > 0; h--) { dst = dest; for (i = w; i > 0; i--) *dst++ = menu_sel_color; dest += SCREEN_WIDTH; } } static int parse_hex_color(char *buff) { char *endp = buff; int t = (int) strtoul(buff, &endp, 16); if (endp != buff) #ifdef PSP return ((t<<8)&0xf800) | ((t>>5)&0x07e0) | ((t>>19)&0x1f); #else return ((t>>8)&0xf800) | ((t>>5)&0x07e0) | ((t>>3)&0x1f); #endif return -1; } void menu_init(void) { int c, l; unsigned char *fd = menu_font_data; char buff[256]; FILE *f; // generate default font from fontdata8x8 memset(menu_font_data, 0, sizeof(menu_font_data)); for (c = 0; c < 256; c++) { for (l = 0; l < 8; l++) { unsigned char fd8x8 = fontdata8x8[c*8+l]; if (fd8x8&0x80) *fd |= 0xf0; if (fd8x8&0x40) *fd |= 0x0f; fd++; if (fd8x8&0x20) *fd |= 0xf0; if (fd8x8&0x10) *fd |= 0x0f; fd++; if (fd8x8&0x08) *fd |= 0xf0; if (fd8x8&0x04) *fd |= 0x0f; fd++; if (fd8x8&0x02) *fd |= 0xf0; if (fd8x8&0x01) *fd |= 0x0f; fd++; } fd += 8*2/2; // 2 empty lines } // load custom font and selector (stored as 1st symbol in font table) readpng(menu_font_data, "skin/font.png", READPNG_FONT); memcpy(menu_font_data, menu_font_data + ((int)'>')*4*10, 4*10); // default selector symbol is '>' readpng(menu_font_data, "skin/selector.png", READPNG_SELECTOR); // load custom colors f = fopen("skin/skin.txt", "r"); if (f != NULL) { lprintf("found skin.txt\n"); while (!feof(f)) { fgets(buff, sizeof(buff), f); if (buff[0] == '#' || buff[0] == '/') continue; // comment if (buff[0] == '\r' || buff[0] == '\n') continue; // empty line if (strncmp(buff, "text_color=", 11) == 0) { int tmp = parse_hex_color(buff+11); if (tmp >= 0) menu_text_color = tmp; else lprintf("skin.txt: parse error for text_color\n"); } else if (strncmp(buff, "selection_color=", 16) == 0) { int tmp = parse_hex_color(buff+16); if (tmp >= 0) menu_sel_color = tmp; else lprintf("skin.txt: parse error for selection_color\n"); } else lprintf("skin.txt: parse error: %s\n", buff); } fclose(f); } } int me_id2offset(const menu_entry *ent, menu_id id) { int i; for (i = 0; ent->name; ent++, i++) if (ent->id == id) return i; lprintf("%s: id %i not found\n", __FUNCTION__, id); return 0; } void me_enable(menu_entry *entries, menu_id id, int enable) { int i = me_id2offset(entries, id); entries[i].enabled = enable; } int me_count(const menu_entry *ent) { int ret; for (ret = 0; ent->name; ent++, ret++) ; return ret; } menu_id me_index2id(const menu_entry *ent, int index) { const menu_entry *last; for (; ent->name; ent++) { if (ent->enabled) { if (index == 0) break; index--; } last = ent; } if (ent->name == NULL) ent = last; return ent->id; } /* TODO rm */ void me_draw(const menu_entry *entries, int count, int x, int y, me_draw_custom_f *cust_draw, void *param) { int i, y1 = y; for (i = 0; i < count; i++) { if (!entries[i].enabled) continue; if (entries[i].name == NULL) { if (cust_draw != NULL) cust_draw(&entries[i], x, y1, param); y1 += 10; continue; } text_out16(x, y1, entries[i].name); if (entries[i].beh == MB_OPT_ONOFF) text_out16(x + 27*8, y1, (*(int *)entries[i].var & entries[i].mask) ? "ON" : "OFF"); else if (entries[i].beh == MB_OPT_RANGE) text_out16(x + 27*8, y1, "%i", *(int *)entries[i].var); y1 += 10; } } static void me_draw2(const menu_entry *entries, int sel) { const menu_entry *ent; int x, y, w = 0, h = 0; int opt_offs = 27*8; const char *name; int asel = 0; int i, n; /* calculate size of menu rect */ for (ent = entries, i = n = 0; ent->name; ent++, i++) { int wt; if (!ent->enabled) continue; if (i == sel) asel = n; name = NULL; wt = strlen(ent->name) * 8; /* FIXME: unhardcode font width */ if (wt == 0 && ent->generate_name) name = ent->generate_name(1); if (name != NULL) wt = strlen(name) * 8; if (ent->beh != MB_NONE) { if (wt > opt_offs) opt_offs = wt + 8; wt = opt_offs; switch (ent->beh) { case MB_NONE: break; case MB_OPT_ONOFF: case MB_OPT_RANGE: wt += 8*3; break; case MB_OPT_CUSTOM: name = NULL; if (ent->generate_name != NULL) name = ent->generate_name(0); if (name != NULL) wt += strlen(name) * 8; break; } } if (wt > w) w = wt; n++; } h = n * 10; w += 16; /* selector */ if (w > SCREEN_WIDTH) { lprintf("width %d > %d\n", w, SCREEN_WIDTH); w = SCREEN_WIDTH; } if (h > SCREEN_HEIGHT) { lprintf("height %d > %d\n", w, SCREEN_HEIGHT); h = SCREEN_HEIGHT; } x = SCREEN_WIDTH / 2 - w / 2; y = SCREEN_HEIGHT / 2 - h / 2; /* draw */ plat_video_menu_begin(); menu_draw_selection(x, y + asel * 10, w); for (ent = entries; ent->name; ent++) { if (!ent->enabled) continue; name = ent->name; if (strlen(name) == 0) { if (ent->generate_name) name = ent->generate_name(1); } if (name != NULL) text_out16(x + 16, y, name); switch (ent->beh) { case MB_NONE: break; case MB_OPT_ONOFF: text_out16(x + 16 + opt_offs, y, (*(int *)ent->var & ent->mask) ? "ON" : "OFF"); break; case MB_OPT_RANGE: text_out16(x + 16 + opt_offs, y, "%i", *(int *)ent->var); break; case MB_OPT_CUSTOM: name = NULL; if (ent->generate_name) name = ent->generate_name(0); if (name != NULL) text_out16(x + 16 + opt_offs, y, "%s", name); break; } y += 10; } plat_video_menu_end(); } int me_process(menu_entry *entries, menu_id id, int is_next) { int i = me_id2offset(entries, id); menu_entry *entry = &entries[i]; switch (entry->beh) { case MB_OPT_ONOFF: *(int *)entry->var ^= entry->mask; return 1; case MB_OPT_RANGE: *(int *)entry->var += is_next ? 1 : -1; if (*(int *)entry->var < (int)entry->min) *(int *)entry->var = (int)entry->min; if (*(int *)entry->var > (int)entry->max) *(int *)entry->var = (int)entry->max; return 1; default: return 0; } } static void me_loop(menu_entry *menu, int *menu_sel) { int ret, inp, sel = *menu_sel, menu_sel_max; menu_sel_max = me_count(menu) - 1; if (menu_sel_max < 1) { lprintf("no enabled menu entries\n"); return; } while (!menu[sel].enabled && sel < menu_sel_max) sel++; /* make sure action buttons are not pressed on entering menu */ me_draw2(menu, sel); while (in_menu_wait_any(50) & (PBTN_MOK|PBTN_MBACK|PBTN_MENU)); for (;;) { me_draw2(menu, sel); inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_MOK|PBTN_MBACK|PBTN_MENU|PBTN_L|PBTN_R); if (inp & PBTN_UP ) { do { sel--; if (sel < 0) sel = menu_sel_max; } while (!menu[sel].enabled); } if (inp & PBTN_DOWN) { do { sel++; if (sel > menu_sel_max) sel = 0; } while (!menu[sel].enabled); } // if ((inp & (PBTN_L|PBTN_R)) == (PBTN_L|PBTN_R)) debug_menu_loop(); // TODO if (inp & (PBTN_MENU|PBTN_MBACK)) break; if (inp & PBTN_MOK) { if (menu[sel].submenu_handler != NULL) { ret = menu[sel].submenu_handler(menu[sel].id); if (ret) break; } } // menuErrorMsg[0] = 0; // TODO: clear error msg } *menu_sel = sel; } /* ***************************************** */ /* TODO s */ int menu_loop_tray(void) { return 0; } void menu_romload_prepare(const char *rom_name) {} void menu_romload_end(void) {} me_bind_action emuctrl_actions[1]; menu_entry opt_entries[1]; menu_entry opt2_entries[1]; menu_entry cdopt_entries[1]; menu_entry ctrlopt_entries[1]; const int opt_entry_count = 0; const int opt2_entry_count = 0; const int cdopt_entry_count = 0; const int ctrlopt_entry_count = 0; extern int engineState; int savestate_menu_loop(int a) { return 1; } int menu_loop_options() { return 1; } void kc_sel_loop() {} void draw_menu_credits() {} void patches_menu_loop() {} // ------------ main menu ------------ static int main_menu_handler(menu_id id) { int ret; switch (id) { case MA_MAIN_RESUME_GAME: if (rom_loaded) { while (in_menu_wait_any(50) & PBTN_MOK); engineState = PGS_Running; return 1; } break; case MA_MAIN_SAVE_STATE: if (rom_loaded) { if (savestate_menu_loop(0)) break; engineState = PGS_Running; return 1; } break; case MA_MAIN_LOAD_STATE: if (rom_loaded) { if (savestate_menu_loop(1)) break; while (in_menu_wait_any(50) & PBTN_MOK); engineState = PGS_Running; return 1; } break; case MA_MAIN_RESET_GAME: if (rom_loaded) { emu_ResetGame(); while (in_menu_wait_any(50) & PBTN_MOK); engineState = PGS_Running; return 1; } break; case MA_MAIN_LOAD_ROM: { /* char curr_path[PATH_MAX], *selfname; FILE *tstf; if ( (tstf = fopen(loadedRomFName, "rb")) ) { fclose(tstf); strcpy(curr_path, loadedRomFName); } else getcwd(curr_path, PATH_MAX); selfname = romsel_loop(curr_path); if (selfname) { printf("selected file: %s\n", selfname); engineState = PGS_ReloadRom; return; }*/ break; } case MA_MAIN_OPTIONS: ret = menu_loop_options(); if (ret == 1) break; // status update if (engineState == PGS_ReloadRom) return 1; // BIOS test break; case MA_MAIN_CONTROLS: kc_sel_loop(); break; case MA_MAIN_CREDITS: draw_menu_credits(); usleep(500*1000); /* FIXME */ in_menu_wait(PBTN_MOK|PBTN_MBACK); break; case MA_MAIN_EXIT: engineState = PGS_Quit; return 1; case MA_MAIN_PATCHES: if (rom_loaded && PicoPatches) { patches_menu_loop(); PicoPatchApply(); strcpy(menuErrorMsg, "Patches applied"); } break; default: lprintf("%s: something unknown selected\n", __FUNCTION__); break; } return 0; } menu_entry e_main_menu[] = { mee_submenu_id("Resume game", MA_MAIN_RESUME_GAME, main_menu_handler), mee_submenu_id("Save State", MA_MAIN_SAVE_STATE, main_menu_handler), mee_submenu_id("Load State", MA_MAIN_LOAD_STATE, main_menu_handler), mee_submenu_id("Reset game", MA_MAIN_RESET_GAME, main_menu_handler), mee_submenu_id("Load new ROM/ISO", MA_MAIN_LOAD_ROM, main_menu_handler), mee_submenu_id("Change options", MA_MAIN_OPTIONS, main_menu_handler), mee_submenu_id("Credits", MA_MAIN_CREDITS, main_menu_handler), mee_submenu_id("Patches / GameGenie",MA_MAIN_PATCHES, main_menu_handler), mee_submenu_id("Exit", MA_MAIN_EXIT, main_menu_handler), mee_end, }; void menu_loop(void) { static int sel = 0; me_enable(e_main_menu, MA_MAIN_RESUME_GAME, rom_loaded); me_enable(e_main_menu, MA_MAIN_SAVE_STATE, rom_loaded); me_enable(e_main_menu, MA_MAIN_LOAD_STATE, rom_loaded); me_enable(e_main_menu, MA_MAIN_RESET_GAME, rom_loaded); me_enable(e_main_menu, MA_MAIN_PATCHES, PicoPatches != NULL); plat_video_menu_enter(rom_loaded); in_set_blocking(1); me_loop(e_main_menu, &sel); in_set_blocking(0); if (rom_loaded) { while (in_menu_wait_any(50) & (PBTN_MENU|PBTN_MBACK)); // wait until select is released engineState = PGS_Running; } } // ------------ debug menu ------------ #include #include #include #include void SekStepM68k(void); static void mplayer_loop(void) { emu_startSound(); while (1) { PDebugZ80Frame(); if (in_menu_wait_any(0) & PBTN_NORTH) break; emu_waitSound(); } emu_endSound(); } static void draw_text_debug(const char *str, int skip, int from) { const char *p; int len, line; p = str; while (skip-- > 0) { while (*p && *p != '\n') p++; if (*p == 0 || p[1] == 0) return; p++; } str = p; for (line = from; line < SCREEN_HEIGHT/10; line++) { while (*p && *p != '\n') p++; len = p - str; if (len > 55) len = 55; smalltext_out16_lim(1, line*10, str, 0xffff, len); if (*p == 0) break; p++; str = p; } } static void draw_frame_debug(void) { char layer_str[48] = "layers: "; if (PicoDrawMask & PDRAW_LAYERB_ON) memcpy(layer_str + 8, "B", 1); if (PicoDrawMask & PDRAW_LAYERA_ON) memcpy(layer_str + 10, "A", 1); if (PicoDrawMask & PDRAW_SPRITES_LOW_ON) memcpy(layer_str + 12, "spr_lo", 6); if (PicoDrawMask & PDRAW_SPRITES_HI_ON) memcpy(layer_str + 19, "spr_hi", 6); clear_screen(); emu_forcedFrame(0); smalltext_out16(4, SCREEN_HEIGHT-8, layer_str, 0xffff); } void debug_menu_loop(void) { int inp, mode = 0; int spr_offs = 0, dumped = 0; char *tmp; while (1) { switch (mode) { case 0: plat_video_menu_begin(); tmp = PDebugMain(); emu_platformDebugCat(tmp); draw_text_debug(tmp, 0, 0); if (dumped) { smalltext_out16(SCREEN_WIDTH-6*10, SCREEN_HEIGHT-8, "dumped", 0xffff); dumped = 0; } break; case 1: draw_frame_debug(); break; case 2: clear_screen(); emu_forcedFrame(0); darken_screen(); PDebugShowSpriteStats((unsigned short *)SCREEN_BUFFER + (SCREEN_HEIGHT/2 - 240/2)*SCREEN_WIDTH + SCREEN_WIDTH/2 - 320/2, SCREEN_WIDTH); break; case 3: clear_screen(); PDebugShowPalette(SCREEN_BUFFER, SCREEN_WIDTH); PDebugShowSprite((unsigned short *)SCREEN_BUFFER + SCREEN_WIDTH*120+SCREEN_WIDTH/2+16, SCREEN_WIDTH, spr_offs); draw_text_debug(PDebugSpriteList(), spr_offs, 6); break; } plat_video_menu_end(); inp = in_menu_wait(PBTN_EAST|PBTN_MBACK|PBTN_WEST|PBTN_NORTH|PBTN_L|PBTN_R|PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT); if (inp & PBTN_MBACK) return; if (inp & PBTN_L) { mode--; if (mode < 0) mode = 3; } if (inp & PBTN_R) { mode++; if (mode > 3) mode = 0; } switch (mode) { case 0: if (inp & PBTN_EAST) SekStepM68k(); if (inp & PBTN_NORTH) { while (inp & PBTN_NORTH) inp = in_menu_wait_any(-1); mplayer_loop(); } if ((inp & (PBTN_WEST|PBTN_LEFT)) == (PBTN_WEST|PBTN_LEFT)) { mkdir("dumps", 0777); PDebugDumpMem(); while (inp & PBTN_WEST) inp = in_menu_wait_any(-1); dumped = 1; } break; case 1: if (inp & PBTN_LEFT) PicoDrawMask ^= PDRAW_LAYERB_ON; if (inp & PBTN_RIGHT) PicoDrawMask ^= PDRAW_LAYERA_ON; if (inp & PBTN_DOWN) PicoDrawMask ^= PDRAW_SPRITES_LOW_ON; if (inp & PBTN_UP) PicoDrawMask ^= PDRAW_SPRITES_HI_ON; if (inp & PBTN_EAST) { PsndOut = NULL; // just in case PicoSkipFrame = 1; PicoFrame(); PicoSkipFrame = 0; while (inp & PBTN_EAST) inp = in_menu_wait_any(-1); } break; case 3: if (inp & PBTN_DOWN) spr_offs++; if (inp & PBTN_UP) spr_offs--; if (spr_offs < 0) spr_offs = 0; break; } } } #endif // !UIQ3 // ------------ util ------------ const char *me_region_name(unsigned int code, int auto_order) { static const char *names[] = { "Auto", " Japan NTSC", " Japan PAL", " USA", " Europe" }; static const char *names_short[] = { "", " JP", " JP", " US", " EU" }; int u, i = 0; if (code) { code <<= 1; while((code >>= 1)) i++; if (i > 4) return "unknown"; return names[i]; } else { static char name[24]; strcpy(name, "Auto:"); for (u = 0; u < 3; u++) { i = 0; code = ((auto_order >> u*4) & 0xf) << 1; while((code >>= 1)) i++; strcat(name, names_short[i]); } return name; } } /* TODO: rename */ void menu_darken_bg(void *dst, int pixels, int darker) { unsigned int *screen = dst; pixels /= 2; if (darker) { while (pixels--) { unsigned int p = *screen; *screen++ = ((p&0xf79ef79e)>>1) - ((p&0xc618c618)>>3); } } else { while (pixels--) { unsigned int p = *screen; *screen++ = (p&0xf79ef79e)>>1; } } }