#include #include "core.h" #include "main.h" #include "menu.h" #include "options.h" #include "overrides.h" #include "plat.h" #include "scale.h" #include "util.h" static int drew_alt_bg = 0; static char cores_path[MAX_PATH]; static struct dirent **corelist = NULL; static int corelist_len = 0; #define MENU_ALIGN_LEFT 0 #define MENU_X2 0 #define MENU_ITEMS_PER_PAGE 11 typedef enum { MA_NONE = 1, MA_MAIN_RESUME_GAME, MA_MAIN_SAVE_STATE, MA_MAIN_LOAD_STATE, MA_MAIN_DISC_CTRL, MA_MAIN_CORE_SEL, MA_MAIN_CONTENT_SEL, MA_MAIN_RESET_GAME, MA_MAIN_CREDITS, MA_MAIN_EXIT, MA_OPT_CORE_OPTS, MA_OPT_SAVECFG, MA_OPT_SAVECFG_GAME, MA_OPT_RMCFG_GAME, MA_CTRL_PLAYER1, MA_CTRL_EMU, } menu_id; me_bind_action me_ctrl_actions[] = { { "UP ", 1 << RETRO_DEVICE_ID_JOYPAD_UP}, { "DOWN ", 1 << RETRO_DEVICE_ID_JOYPAD_DOWN }, { "LEFT ", 1 << RETRO_DEVICE_ID_JOYPAD_LEFT }, { "RIGHT ", 1 << RETRO_DEVICE_ID_JOYPAD_RIGHT }, { "A BUTTON ", 1 << RETRO_DEVICE_ID_JOYPAD_A }, { "B BUTTON ", 1 << RETRO_DEVICE_ID_JOYPAD_B }, { "X BUTTON ", 1 << RETRO_DEVICE_ID_JOYPAD_X }, { "Y BUTTON ", 1 << RETRO_DEVICE_ID_JOYPAD_Y }, { "START ", 1 << RETRO_DEVICE_ID_JOYPAD_START }, { "SELECT ", 1 << RETRO_DEVICE_ID_JOYPAD_SELECT }, { "L BUTTON ", 1 << RETRO_DEVICE_ID_JOYPAD_L }, { "R BUTTON ", 1 << RETRO_DEVICE_ID_JOYPAD_R }, { "L2 BUTTON ", 1 << RETRO_DEVICE_ID_JOYPAD_L2 }, { "R2 BUTTON ", 1 << RETRO_DEVICE_ID_JOYPAD_R2 }, { NULL, 0 } }; /* Must be a superset of all possible actions. This is used when * saving config, and if an entry isn't here, the saver won't see * it. */ me_bind_action emuctrl_actions[] = { { "Save State ", 1 << EACTION_SAVE_STATE }, { "Load State ", 1 << EACTION_LOAD_STATE }, { "Toggle FPS/CPU% ", 1 << EACTION_TOGGLE_HUD }, { "Toggle FF ", 1 << EACTION_TOGGLE_FF }, { "Take Screenshot ", 1 << EACTION_SCREENSHOT }, { NULL, 0 } }; static int emu_check_save_file(int slot, int *time) { char fname[MAX_PATH]; struct stat status; int ret; state_file_name(fname, sizeof(fname), slot); ret = stat(fname, &status); if (ret != 0) return 0; return 1; } static int emu_save_load_game(int load, int unused) { int ret; if (load) ret = state_read(); else ret = state_write(); return ret; } // RGB565 static unsigned short fname2color(const char *fname) { return 0xFFFF; } #include "libpicofe/menu.c" static void draw_menu_message(const char *msg, void (*draw_more)(void)) __attribute__((unused)); static const char *mgn_saveloadcfg(int id, int *offs) { return ""; } static int mh_restore_defaults(int id, int keys) { set_defaults(); menu_update_msg("defaults restored"); return 1; } static int mh_savecfg(int id, int keys) { if (save_config(id == MA_OPT_SAVECFG_GAME ? 1 : 0) == 0) menu_update_msg("config saved"); else menu_update_msg("failed to write config"); return 1; } static int mh_rmcfg(int id, int keys) { if (remove_config(id == MA_OPT_RMCFG_GAME ? 1 : 0) == 0) menu_update_msg("config removed"); else menu_update_msg("failed to remove config"); return 1; } static void draw_src_bg(void) { memcpy(g_menubg_ptr, g_menubg_src_ptr, g_menubg_src_h * g_menubg_src_pp * sizeof(uint16_t)); menu_darken_bg(g_menubg_ptr, g_menubg_src_ptr, g_menubg_src_h * g_menubg_src_pp, 0); } static int mh_set_core(int id, int keys) { if (corelist && id < corelist_len) snprintf(core_path, sizeof(core_path), "%s/%s", cores_path, corelist[id]->d_name); return 1; } static int core_selector(const struct dirent *ent) { return has_suffix_i(ent->d_name, "_libretro.so"); } static int menu_loop_core_page(int offset, int keys) { static int sel = 0; menu_entry e_menu_cores[MENU_ITEMS_PER_PAGE + 2] = {0}; /* +2 for Next, NULL */ size_t menu_idx = 0; int i; char names[MENU_ITEMS_PER_PAGE][MAX_PATH]; for (i = offset, menu_idx = 0; i < corelist_len && menu_idx < MENU_ITEMS_PER_PAGE; i++) { menu_entry *option; struct dirent *ent = corelist[i]; option = &e_menu_cores[menu_idx]; core_extract_name(ent->d_name, names[menu_idx], sizeof(names[menu_idx])); option->name = names[menu_idx]; option->beh = MB_OPT_CUSTOM; option->id = i; option->enabled = 1; option->selectable = 1; option->handler = mh_set_core; menu_idx++; } if (i < corelist_len) { menu_entry *option; option = &e_menu_cores[menu_idx]; option->name = "Next page"; option->beh = MB_OPT_CUSTOM; option->id = i; option->enabled = 1; option->selectable = 1; option->handler = menu_loop_core_page; } return me_loop(e_menu_cores, &sel); } int menu_select_core(void) { int ret = -1; getcwd(cores_path, MAX_PATH); corelist_len = scandir(cores_path, &corelist, core_selector, alphasort); if (!corelist_len) return -1; plat_video_menu_enter(1); if (menu_loop_core_page(0, 0) < 0) goto finish; if (core_path[0] == '\0') goto finish; ret = 0; finish: /* wait until menu, ok, back is released */ while (in_menu_wait_any(NULL, 50) & (PBTN_MENU|PBTN_MOK|PBTN_MBACK)) ; plat_video_menu_leave(); if (corelist_len > 0) { while (corelist_len--) free(corelist[corelist_len]); free(corelist); corelist = NULL; } return ret; } int hidden_file_filter(struct dirent **namelist, int count, const char *basedir) { int newcount = 0; for (int i = 0; i < count; i++) { if (namelist[i]->d_name[0] == '.' && namelist[i]->d_name[1] != '.') { free(namelist[i]); namelist[i] = NULL; } } for (int i = 0; i < count; i++) { if (namelist[i] != NULL) namelist[newcount++] = namelist[i]; } return newcount; } const char *select_content(void) { const char *fname = NULL; const char **extensions = core_extensions(); const char **exts_with_zip = NULL; int i = 0, size = 0; if (content_path[0] == '\0') { if (getenv("CONTENT_DIR")) { strncpy(content_path, getenv("CONTENT_DIR"), sizeof(content_path) - 1); #ifdef CONTENT_DIR } else { strncpy(content_path, CONTENT_DIR, sizeof(content_path) - 1); #else } else if (getenv("HOME")) { strncpy(content_path, getenv("HOME"), sizeof(content_path) - 1); #endif } } if (extensions) { for (size = 0; extensions[size]; size++) ; } exts_with_zip = calloc(size + 2, sizeof (char *)); /* add 2 for "zip", NULL */ if (exts_with_zip) { for (i = 0; extensions[i]; i++) { exts_with_zip[i] = extensions[i]; } exts_with_zip[i] = "zip"; } else { exts_with_zip = extensions; } fname = menu_loop_romsel(content_path, sizeof(content_path), exts_with_zip, hidden_file_filter); if (exts_with_zip != extensions) free(exts_with_zip); return fname; } int menu_select_content(void) { const char *fname = NULL; int ret = -1; plat_video_menu_enter(1); fname = select_content(); if (!fname) goto finish; strncpy(content_path, fname, sizeof(content_path) - 1); set_defaults(); load_config(); if (g_autostateld_opt) resume_slot = 0; ret = 0; finish: /* wait until menu, ok, back is released */ while (in_menu_wait_any(NULL, 50) & (PBTN_MENU|PBTN_MOK|PBTN_MBACK)) ; plat_video_menu_leave(); return ret; } static int menu_loop_select_content(int id, int keys) { const char *fname = select_content(); if (fname == NULL) return -1; core_unload_content(); strncpy(content_path, fname, sizeof(content_path) - 1); set_defaults(); if (core_load_content(fname)) { quit(-1); } load_config(); if (plat_reinit()) { quit(-1); } load_config_keys(); if (g_autostateld_opt) { resume_slot = 0; state_resume(); } return 1; } static int menu_loop_disc(int id, int keys) { static int sel = 0; menu_entry e_menu_disc_options[2] = {0}; unsigned disc = disc_get_index() + 1; menu_entry *option = &e_menu_disc_options[0]; option->name = "Disc"; option->beh = MB_OPT_RANGE; option->var = &disc; option->min = 1; option->max = disc_get_count(); option->enabled = 1; option->need_to_save = 1; option->selectable = 1; me_loop(e_menu_disc_options, &sel); if (disc_get_index() + 1 != disc) disc_switch_index(disc - 1); return 0; } static int menu_loop_core_options_page(int offset, int keys) { static int sel = 0; menu_entry *e_menu_core_options; size_t i, menu_idx; /* core_option + 2 for possible "Next page" + NULL */ e_menu_core_options = (menu_entry *)calloc(core_options.visible_len + 2, sizeof(menu_entry)); if (!e_menu_core_options) { PA_ERROR("Error allocating core options\n"); return 0; } for (i = offset, menu_idx = 0; i < core_options.len && menu_idx < MENU_ITEMS_PER_PAGE; i++) { struct core_option_entry *entry = &core_options.entries[i]; menu_entry *option; const char *key = entry->key; if (entry->blocked || !entry->visible) continue; option = &e_menu_core_options[menu_idx]; option->name = entry->desc; option->beh = MB_OPT_ENUM; option->var = options_get_value_ptr(key); option->enabled = 1; option->need_to_save = 1; option->selectable = 1; option->data = options_get_options(key); option->help = entry->info; menu_idx++; } if (i < core_options.len) { menu_entry *option; option = &e_menu_core_options[menu_idx]; option->name = "Next page"; option->beh = MB_OPT_CUSTOM; option->id = i; option->enabled = 1; option->selectable = 1; option->handler = menu_loop_core_options_page; } me_loop(e_menu_core_options, &sel); options_update_changed(); free(e_menu_core_options); return 0; } static int menu_loop_core_options(int id, int keys) { return menu_loop_core_options_page(0, keys); } static const char h_rm_config_game[] = "Removes game-specific config file"; static const char h_restore_def[] = "Switches back to default settings"; static const char h_show_fps[] = "Shows frames and vsyncs per second"; static const char h_show_cpu[] = "Shows CPU usage"; static const char h_audio_buffer_size[] = "The size of the audio buffer, in frames. Higher\n" "values reduce the risk of audio crackling at the\n" "cost of delayed sound."; static const char h_scale_size[] = "How much to stretch the screen when scaling. Native\n" "does no stretching. Aspect uses the correct aspect\n" "ratio. Full uses the whole screen."; static const char h_scale_filter[] = "When stretching, how missing pixels are filled.\n" "Nearest copies the last pixel. Sharp keeps pixels\n" "aligned where possible. Smooth adds a blur effect."; static const char *men_scale_size[] = { "Native", "Aspect", "Full", NULL}; static const char *men_scale_filter[] = { "Nearest", "Sharp", "Smooth", NULL}; static menu_entry e_menu_video_options[] = { mee_onoff_h ("Show FPS", 0, show_fps, 1, h_show_fps), mee_onoff_h ("Show CPU %", 0, show_cpu, 1, h_show_cpu), mee_enum_h ("Screen size", 0, scale_size, men_scale_size, h_scale_size), mee_enum_h ("Filter", 0, scale_filter, men_scale_filter, h_scale_filter), mee_range_h ("Audio buffer", 0, audio_buffer_size, 1, 15, h_audio_buffer_size), mee_end, }; static int menu_loop_video_options(int id, int keys) { static int sel = 0; me_loop(e_menu_video_options, &sel); scale_update_scaler(); return 0; } static int key_config_loop_wrap(int id, int keys) { const struct core_override *override = get_overrides(); me_bind_action *actions = CORE_OVERRIDE(override, actions, me_ctrl_actions); size_t action_size = CORE_OVERRIDE(override, action_size, array_size(me_ctrl_actions)); me_bind_action *emu_actions = emuctrl_actions; size_t emu_action_size = array_size(emuctrl_actions); switch (id) { case MA_CTRL_PLAYER1: key_config_loop(actions, action_size - 1, 0); break; case MA_CTRL_EMU: key_config_loop(emu_actions, emu_action_size - 1, -1); break; default: break; } return 0; } const char *config_label(int id, int *offs) { return config_override ? "Loaded: game config" : "Loaded: global config"; } static menu_entry e_menu_config_options[] = { mee_cust_nosave ("Save global config", MA_OPT_SAVECFG, mh_savecfg, mgn_saveloadcfg), mee_cust_nosave ("Save game config", MA_OPT_SAVECFG_GAME, mh_savecfg, mgn_saveloadcfg), mee_handler_id_h ("Delete game config", MA_OPT_RMCFG_GAME, mh_rmcfg, h_rm_config_game), mee_handler_h ("Restore defaults", mh_restore_defaults, h_restore_def), mee_label (""), mee_label_mk (0, config_label), mee_end, }; static int menu_loop_config_options(int id, int keys) { static int sel = 0; me_enable(e_menu_config_options, MA_OPT_RMCFG_GAME, config_override == 1); me_loop(e_menu_config_options, &sel); return 0; } static menu_entry e_menu_options[] = { mee_handler ("Audio and video", menu_loop_video_options), mee_handler_id("Emulator options", MA_OPT_CORE_OPTS, menu_loop_core_options), mee_handler_id("Player controls", MA_CTRL_PLAYER1, key_config_loop_wrap), mee_handler_id("Emulator controls", MA_CTRL_EMU, key_config_loop_wrap), mee_handler ("Save config", menu_loop_config_options), mee_end, }; static int menu_loop_options(int id, int keys) { static int sel = 0; me_loop(e_menu_options, &sel); return 0; } static int main_menu_handler(int id, int keys) { switch (id) { case MA_MAIN_RESUME_GAME: return 1; case MA_MAIN_SAVE_STATE: return menu_loop_savestate(0); case MA_MAIN_LOAD_STATE: return menu_loop_savestate(1); case MA_MAIN_RESET_GAME: current_core.retro_reset(); return 1; case MA_MAIN_EXIT: should_quit = 1; return 1; default: lprintf("%s: something unknown selected\n", __FUNCTION__); break; } return 0; } static menu_entry e_menu_main[] = { mee_handler_id("Resume game", MA_MAIN_RESUME_GAME, main_menu_handler), mee_handler_id("Save state", MA_MAIN_SAVE_STATE, main_menu_handler), mee_handler_id("Load state", MA_MAIN_LOAD_STATE, main_menu_handler), mee_handler_id("Disc control", MA_MAIN_DISC_CTRL, menu_loop_disc), /* mee_handler_id("Cheats", MA_MAIN_CHEATS, main_menu_handler), */ mee_handler ("Options", menu_loop_options), mee_handler_id("Reset game", MA_MAIN_RESET_GAME, main_menu_handler), mee_handler_id("Load new game", MA_MAIN_CONTENT_SEL, menu_loop_select_content), mee_handler_id("Exit", MA_MAIN_EXIT, main_menu_handler), mee_end, }; static void draw_savestate_bg(int slot) { char filename[MAX_PATH]; int w, h, bpp; size_t bufsize = SCREEN_PITCH * SCREEN_HEIGHT; void *buf = calloc(bufsize, sizeof(char)); if (!buf) { PA_WARN("Couldn't allocate savestate background"); goto finish; } state_file_name(filename, MAX_PATH, slot); if (plat_load_screen(filename, buf, bufsize, &w, &h, &bpp)) goto finish; if (bpp == sizeof(uint16_t)) { menu_darken_bg(g_menubg_ptr, buf, w * h, 0); drew_alt_bg = 1; } finish: if (buf) free(buf); } void menu_begin(void) { if (!drew_alt_bg) draw_src_bg(); } void menu_end(void) { drew_alt_bg = 0; } void menu_loop(void) { static int sel = 0; bool needs_disc_ctrl = disc_get_count() > 1; plat_video_menu_enter(1); me_enable(e_menu_options, MA_OPT_CORE_OPTS, core_options.visible_len > 0); me_enable(e_menu_main, MA_MAIN_SAVE_STATE, state_allowed()); me_enable(e_menu_main, MA_MAIN_LOAD_STATE, state_allowed()); me_enable(e_menu_main, MA_MAIN_DISC_CTRL, needs_disc_ctrl); #ifdef MMENU if (state_allowed()) { me_enable(e_menu_main, MA_MAIN_SAVE_STATE, mmenu == NULL); me_enable(e_menu_main, MA_MAIN_LOAD_STATE, mmenu == NULL); } #endif me_loop(e_menu_main, &sel); /* wait until menu, ok, back is released */ while (in_menu_wait_any(NULL, 50) & (PBTN_MENU|PBTN_MOK|PBTN_MBACK)) ; /* Force the hud to clear */ plat_video_set_msg(NULL, 0, 0); plat_video_menu_leave(); } int menu_init(void) { menu_init_base(); g_menubg_src_ptr = calloc(g_menubg_src_pp * g_menubg_src_h, sizeof(uint16_t)); g_menubg_ptr = calloc(g_menuscreen_w * g_menuscreen_pp, sizeof(uint16_t)); if (g_menubg_src_ptr == NULL || g_menubg_ptr == NULL) { fprintf(stderr, "OOM\n"); return -1; } return 0; } void menu_finish(void) { if (g_menubg_src_ptr) { free(g_menubg_src_ptr); g_menubg_src_ptr = NULL; } if (g_menubg_ptr) { free(g_menubg_ptr); g_menubg_ptr = NULL; } } static void debug_menu_loop(void) { } void menu_update_msg(const char *msg) { strncpy(menu_error_msg, msg, sizeof(menu_error_msg)); menu_error_msg[sizeof(menu_error_msg) - 1] = 0; menu_error_time = plat_get_ticks_ms(); PA_INFO("%s\n", menu_error_msg); }