From dc05a3263600f1f0bd5df2e01b5310534af12e53 Mon Sep 17 00:00:00 2001 From: Nathaniel Walizer Date: Wed, 15 Jan 2025 22:52:24 -0800 Subject: [PATCH] Add in-game menu --- src/cart.c | 27 ------- src/cart.h | 2 - src/input.h | 13 ++-- src/nes.c | 13 +++- src/nes.h | 4 + src/nese.c | 199 ++++++++++++++++++++++++++++++++++++----------- src/render.h | 7 +- src/save.c | 22 +++--- src/sdl_input.c | 6 +- src/sdl_menu.c | 182 +++++++++++++++++++++++++++++-------------- src/sdl_render.c | 17 +++- 11 files changed, 331 insertions(+), 161 deletions(-) diff --git a/src/cart.c b/src/cart.c index faadd3e..bc85850 100644 --- a/src/cart.c +++ b/src/cart.c @@ -108,30 +108,3 @@ int nes_cart_init_file(nes_cart* cart, FILE* file) { return status; } - - -FILE* nes_load_cart(nes_cart* cart, const char* cart_filename) { - int status = 0; - - FILE* cart_file = fopen(cart_filename, "rb"); - if (NULL == cart_file) { - status = -1; - fprintf(stderr, "Could not open %s\n", cart_filename); - } - - if (status == 0) { - status = nes_cart_init_file(cart, cart_file); - } - - if (status == 0) { - // Failure might mean there's nothing to load - load_sram(cart, cart_filename); - } - - if (status != 0 && NULL != cart_file) { - fclose(cart_file); - cart_file = NULL; - } - - return cart_file; -} diff --git a/src/cart.h b/src/cart.h index 2bd31b4..eaa61c5 100644 --- a/src/cart.h +++ b/src/cart.h @@ -28,7 +28,5 @@ int nes_cart_init_file(nes_cart*, FILE* file); int nes_cart_init_mem(nes_cart*, void*, int len); void nes_cart_done(nes_cart*); -FILE* nes_load_cart(nes_cart* cart, const char* cart_filename); - #endif // NES_CART_H_ diff --git a/src/input.h b/src/input.h index ba5156d..4f88331 100644 --- a/src/input.h +++ b/src/input.h @@ -9,12 +9,13 @@ typedef enum { input_Result_Error = -1, - input_Result_OK = 0, - input_Result_Quit = 1, - input_Result_Reset = 2, - input_Result_Save = 3, - input_Result_Load = 4, - input_Result_Cancel = 5, + input_Result_OK, + input_Result_Quit, + input_Result_Menu, + input_Result_Reset, + input_Result_Save, + input_Result_Load, + input_Result_Cancel, } nes_Input_Result; #define nes_controller_num_buttons (8U) diff --git a/src/nes.c b/src/nes.c index c39cda4..1ddbf09 100644 --- a/src/nes.c +++ b/src/nes.c @@ -66,7 +66,6 @@ static void nes_irq(void* sys, int active) { int nes_init(nes* sys, int audio_freq) { e6502_init(&sys->cpu, (e6502_Read*)nes_mem_read, (e6502_Write*)nes_mem_write, sys); - nes_map_set_irq(sys->cart.mapper, nes_irq, sys); nes_ppu_init(&sys->ppu, &sys->cart); return nes_apu_init( &sys->apu, @@ -77,6 +76,18 @@ int nes_init(nes* sys, int audio_freq) { ); } +void nes_done(nes* sys) { + nes_cart_done(&sys->cart); + nes_apu_done(&sys->apu); +} + +int nes_setup_cart(nes* sys) { + nes_map_set_irq(sys->cart.mapper, nes_irq, sys); + sys->ppu.mapper = sys->cart.mapper; + nes_reset(sys); + return 0; +} + void nes_reset(nes* sys) { e6502_reset(&sys->cpu); nes_ppu_reset(&sys->ppu); diff --git a/src/nes.h b/src/nes.h index 0b16945..b34c9ca 100644 --- a/src/nes.h +++ b/src/nes.h @@ -58,6 +58,10 @@ void nes_mem_write(nes*, uint16_t addr, uint8_t); int nes_init(nes*, int audio_freq); +void nes_done(nes*); + +int nes_setup_cart(nes*); + void nes_reset(nes*); nes_ppu_Result nes_step(nes*, int* run); diff --git a/src/nese.c b/src/nese.c index f1059db..1dac941 100644 --- a/src/nese.c +++ b/src/nese.c @@ -22,7 +22,32 @@ extern nes_Input_Reader sdl_input; extern nes_Audio_Stream sdl_audio; -static nes sys = {0}; +static FILE* nes_load_cart(nes_cart* cart, + const char* cart_filename) { + int status = 0; + + FILE* cart_file = fopen(cart_filename, "rb"); + if (NULL == cart_file) { + status = -1; + fprintf(stderr, "Could not open %s\n", cart_filename); + } + + if (status == 0) { + status = nes_cart_init_file(cart, cart_file); + } + + if (status == 0) { + // Failure might mean there's nothing to load + load_sram(cart, cart_filename); + } + + if (status != 0 && NULL != cart_file) { + fclose(cart_file); + cart_file = NULL; + } + + return cart_file; +} typedef struct { @@ -32,7 +57,7 @@ typedef struct { int mode; } loadsave_state; -void loadsave_start(loadsave_state* loadsave, int mode) { +static void loadsave_start(loadsave_state* loadsave, int mode) { if (0 != loadsave->mode) { // We can't do two things at once. overlay_clear_message(loadsave->overlay, loadsave->id); @@ -54,18 +79,15 @@ void loadsave_start(loadsave_state* loadsave, int mode) { } } -int loadsave_tick(loadsave_state* loadsave) { +static int loadsave_tick(loadsave_state* loadsave) { int action = 0; if (loadsave->mode != 0 && --loadsave->timer <= 0) { const char *op = NULL; if (input_Result_Load == loadsave->mode) { - //load_state(&sys, cart_filename); op = "restored"; - } else if (input_Result_Save == loadsave->mode) { - //save_state(&sys, cart_filename); op = "saved"; } @@ -84,73 +106,138 @@ int loadsave_tick(loadsave_state* loadsave) { return action; } +typedef struct { + char* filename; + FILE* file; +} cart_info; -int main(int argc, char* argv[]) { - int status = 0; - - nes_Renderer* rend = &sdl_renderer; - if (status == 0) { - status = nes_render_init(rend); - } - - nes_Input_Reader* input = &sdl_input; - if (status == 0) { - status = nes_input_init(input); - } - - nes_Audio_Stream* audio = &sdl_audio; - if (status == 0) { - status = nes_audio_init(audio, audio_freq); - } +static void cleanup_cart_info(cart_info* cart) { + if (cart->file) fclose(cart->file); + free(cart->filename); +} - char* cart_filename = NULL; - FILE* cart_file = NULL; - if (0 == status && argc > 1) { - cart_filename = strdup(argv[1]); - cart_file = nes_load_cart(&sys.cart, argv[1]); - } +static int select_rom(menu_state* menu, nes_Renderer* rend, + nes_Input_Reader* input, nes* sys, + cart_info* cur_cart) { + int status = 0; + cart_info cart = {0}; + nes_cart new_cart = {0}; - menu_state menu = {0}; - while (0 == status && NULL == cart_file) { + while (0 == status && NULL == cart.file) { // Display a load failure message? - if (NULL != cart_filename) { + if (NULL != cart.filename) { char message[1024]; snprintf(message, sizeof(message) - 1, - "Could not load\n%s", cart_filename); - int button = modal_popup(message, rend, input, &sys); + "Could not load\n%s", cart.filename); + int button = modal_popup(message, rend, input, sys); if (input_Result_Quit == (button >> 8)) { // Program closed inside modal - status = -1; + status = input_Result_Quit; } } if (0 == status) { // If we didn't launch with a file, run the loader - cart_filename = run_main_menu(&menu, rend, - input, &sys); + cart.filename = run_main_menu(menu, rend, + input, sys); - if (NULL == cart_filename) { + if ( NULL == cart.filename || + (char*)-1 == cart.filename ) { // This means that we dumped out of the loader + cart.filename = NULL; status = -1; } else { - cart_file = nes_load_cart(&sys.cart, - cart_filename); + cart.file = nes_load_cart(&new_cart, + cart.filename); } } } + if (0 == status && NULL != cart.file) { + save_sram(&sys->cart, cur_cart->filename); + + nes_cart_done(&sys->cart); + cleanup_cart_info(cur_cart); + + sys->cart = new_cart; + *cur_cart = cart; + + nes_setup_cart(sys); + } + + return status; +} + +static int do_game_menu(menu_state* state, nes_Renderer* rend, + nes_Input_Reader* input, nes* sys, + cart_info* cart) { + int status = 0; + menu_state rom_menu = {0}; + + while (1) { + status = run_game_menu(state, rend, input, sys); + + if (input_Result_Menu == status) { + status = select_rom(&rom_menu, rend, input, + sys, cart); + + if (input_Result_Cancel == status) { + continue; + } + } + + break; + } + + return status; +} + + +int main(int argc, char* argv[]) { + int status = 0; + + nes_Renderer* rend = &sdl_renderer; + if (status == 0) { + status = nes_render_init(rend); + } + + nes_Input_Reader* input = &sdl_input; + if (status == 0) { + status = nes_input_init(input); + } + + nes_Audio_Stream* audio = &sdl_audio; + if (status == 0) { + status = nes_audio_init(audio, audio_freq); + } + + nes sys = {0}; if (status == 0) { status = nes_init(&sys, audio_freq); } + cart_info cart = {0}; + if (0 == status && argc > 1) { + cart.filename = strdup(argv[1]); + cart.file = nes_load_cart(&sys.cart, cart.filename); + if (NULL != cart.file) { + nes_setup_cart(&sys); + } + } + + menu_state main_menu = {0}; + if (0 == status && NULL == cart.file) { + status = select_rom(&main_menu, rend, input, + &sys, &cart); + } + if (status == 0) { + menu_state game_menu = {0}; loadsave_state loadsave = { .overlay = &rend->overlay, }; - nes_reset(&sys); - nes_render(rend, &sys.ppu); time_us t_target = time_now(); @@ -170,9 +257,9 @@ int main(int argc, char* argv[]) { // Load/Save Operations int action = loadsave_tick(&loadsave); if (input_Result_Load == action) { - load_state(&sys, cart_filename); + load_state(&sys, cart.filename); } else if (input_Result_Save == action) { - save_state(&sys, cart_filename); + save_state(&sys, cart.filename); } // Sleep to catch up to master clock @@ -197,6 +284,20 @@ int main(int argc, char* argv[]) { // Update button states every rendered frame status = nes_input_update(input, &sys.input); + if (input_Result_Menu == status) { + status = do_game_menu( + &game_menu, rend, + input, &sys, &cart + ); + if ( input_Result_Load == status || + input_Result_Save == status) { + loadsave.mode = status; + loadsave.timer = 0; + status = 0; + } + // Allow other options to fall through + } + if (input_Result_Reset == status) { overlay_add_message( &rend->overlay, @@ -239,11 +340,15 @@ int main(int argc, char* argv[]) { status == 0 ? "OK" : "Halted"); // Failure might mean there's nothing to save - save_sram(&sys.cart, cart_filename); + save_sram(&sys.cart, cart.filename); } - if (NULL != cart_file) fclose(cart_file); - free(cart_filename); + nes_done(&sys); + cleanup_cart_info(&cart); + + nes_audio_done(audio); + nes_input_done(input); + nes_render_done(rend); return status; } diff --git a/src/render.h b/src/render.h index bca3a36..8c77543 100644 --- a/src/render.h +++ b/src/render.h @@ -10,7 +10,7 @@ typedef struct nes_Renderer_t { void (*done)(struct nes_Renderer_t*); int (*render)(struct nes_Renderer_t*, nes_ppu*); - void (*draw_last_frame)(struct nes_Renderer_t*); + void (*draw_last_frame)(struct nes_Renderer_t*, int dim); void (*draw_text)(struct nes_Renderer_t*, const char*, int x, int y); void (*text_size)(struct nes_Renderer_t*, const char*, int* w, int* h); void (*draw_done)(struct nes_Renderer_t*); @@ -31,8 +31,9 @@ static inline int nes_render(nes_Renderer* rend, nes_ppu* ppu) { return rend->render(rend, ppu); } -static inline void nes_draw_last_frame(nes_Renderer* rend) { - rend->draw_last_frame(rend); +static inline void nes_draw_last_frame(nes_Renderer* rend, + int dim) { + rend->draw_last_frame(rend, dim); } static inline void nes_text_size(nes_Renderer* rend, diff --git a/src/save.c b/src/save.c index c62a7cc..b5f9ff3 100644 --- a/src/save.c +++ b/src/save.c @@ -37,16 +37,18 @@ int load_sram(nes_cart* cart, const char* cart_filename) { int save_sram(const nes_cart* cart, const char* cart_filename) { int status = -1; - int sram_size = cart->mapper->sram_size ? - cart->mapper->sram_size(cart->mapper) : 0; - const void* sram = cart->mapper->sram ? - cart->mapper->sram(cart->mapper) : NULL; - - if (sram_size > 0 && NULL != sram) { - char sram_filename[FILENAME_MAX] = {0}; - make_sram_filename(sram_filename, FILENAME_MAX - 1, - cart_filename); - status = write_file(sram_filename, sram, sram_size); + if (NULL != cart->mapper) { + int sram_size = cart->mapper->sram_size ? + cart->mapper->sram_size(cart->mapper) : 0; + const void* sram = cart->mapper->sram ? + cart->mapper->sram(cart->mapper) : NULL; + + if (sram_size > 0 && NULL != sram) { + char sram_filename[FILENAME_MAX] = {0}; + make_sram_filename(sram_filename, FILENAME_MAX - 1, + cart_filename); + status = write_file(sram_filename, sram, sram_size); + } } return status; diff --git a/src/sdl_input.c b/src/sdl_input.c index d3b79d2..d870f8f 100644 --- a/src/sdl_input.c +++ b/src/sdl_input.c @@ -53,7 +53,7 @@ static void sdl_input_done(nes_Input_Reader* input) { sdl_lose_gamepad(input->data); } -static const int sdl_reset_key = SDLK_ESCAPE; +static const int sdl_menu_key = SDLK_ESCAPE; static const int sdl_save_key = SDLK_F1; static const int sdl_load_key = SDLK_F2; @@ -111,9 +111,9 @@ static int sdl_input_update(nes_Input_Reader* reader, input->controllers[0].buttons &= ~mask; } - } else if ( sdl_reset_key == event.key.keysym.sym && + } else if ( sdl_menu_key == event.key.keysym.sym && SDL_KEYDOWN == event.type) { - status = input_Result_Reset; + status = input_Result_Menu; } else if (sdl_save_key == event.key.keysym.sym) { if (SDL_KEYDOWN == event.type) { diff --git a/src/sdl_menu.c b/src/sdl_menu.c index fb998c1..8df2bed 100644 --- a/src/sdl_menu.c +++ b/src/sdl_menu.c @@ -22,7 +22,7 @@ static int get_input(nes_Input_Reader* reader, } static int wait_for_input(nes_Input_Reader* reader, - nes_input* input) { + nes_input* input) { int buttons = input->controllers[0].buttons; int status = 0; for ( ; @@ -95,16 +95,21 @@ static void fix_filename(char* dst, int n, const char* src) { } static inline int n_visible(void) { - return (((nes_ppu_render_h - 30) - 1) / 11) - 1; + return ((nes_ppu_render_h - 20) / 11); } -static void show_menu(const menu_state* menu, - nes_Renderer* rend, file_list* files) { - nes_draw_last_frame(rend); +static void show_menu(const menu_state* menu, int dim, int x, + nes_Renderer* rend, + const file_list* files) { + nes_draw_last_frame(rend, dim); - int bottom = menu->top + n_visible(); + int bottom = menu->top + n_visible() - 1; - for ( int n = menu->top, y = 10; + int max = n_visible(); + if (max > files->count) max = files->count; + int y = (nes_ppu_render_h - (max * 11)) / 2; + + for ( int n = menu->top; n < files->count && n <= bottom; ++n, y += 11 ) { char filename[100]; @@ -115,13 +120,73 @@ static void show_menu(const menu_state* menu, ( (menu->top == n && 0 != menu->top) || (bottom == n && files->count - 1 > bottom) ) ? "..." : filename, - 20, y); - if (menu->cursor == n) nes_draw_text(rend, ">", 10, y); + x, y); + if (menu->cursor == n) { + nes_draw_text(rend, ">", x - 10, y); + } } nes_draw_done(rend); } +static int run_menu(menu_state* state, const file_list* files, + int x, nes_Renderer* rend, + nes_Input_Reader* input, nes* sys) { + menu_state menu = {0}; + if (NULL != state) { + menu = *state; + if (menu.cursor < 0) { + menu.cursor = 0; + } else if (menu.cursor >= files->count) { + menu.cursor = files->count - 1; + } + } + + int status = 0; + while (0 == status) { + // Scrolling (do this first to ensure menu is valid) + const int visible = n_visible() - 1; + menu.top = menu.cursor - (visible / 2); + if (menu.top <= 0) { + // We use <= so we don't readjust the top from 0 + menu.top = 0; + } else if (menu.top + visible >= files->count) { + menu.top = (files->count - 1) - visible; + } + + show_menu(&menu, NULL != sys->cart.mapper, + x, rend, files); + + int buttons = wait_for_input(input, &sys->input); + int special = (buttons >> 8); + + if (input_Result_Quit == special) { + status = special; + + } else if (buttons & (1 << Button_B)) { + status = input_Result_Cancel; + + } else if (buttons & ( (1 << Button_A) | + (1 << Button_Start) )) { + // Select + break; + + } else if (buttons & (1 << Button_Up)) { + if (menu.cursor > 0) --menu.cursor; + + } else if (buttons & ( (1 << Button_Down) | + (1 << Button_Select) )) { + if (menu.cursor < (files->count - 1)) { + ++menu.cursor; + } + } + } + + if (NULL != state) *state = menu; + + return status; +} + char* run_main_menu(menu_state* state, nes_Renderer* rend, nes_Input_Reader* input, nes* sys) { @@ -130,7 +195,7 @@ char* run_main_menu(menu_state* state, nes_Renderer* rend, DIR* dir = opendir("rom"); if (NULL == dir) { - nes_draw_last_frame(rend); + nes_draw_last_frame(rend, NULL != sys->cart.mapper); nes_draw_text(rend, "No ROMS found!", 10, 10); nes_draw_text(rend, "Press any key to exit", 10, 21); nes_draw_done(rend); @@ -142,55 +207,19 @@ char* run_main_menu(menu_state* state, nes_Renderer* rend, closedir(dir); menu_state menu = {0}; - if (NULL != state) { - menu = *state; - if (menu.cursor < 0) { - menu.cursor = 0; - } else if (menu.cursor >= files.count) { - menu.cursor = files.count - 1; - } - } - - while (1) { - // Scrolling (do this first to ensure menu is valid) - const int visible = n_visible(); - menu.top = menu.cursor - (visible / 2); - if (menu.top <= 0) { - // We use <= so we don't readjust the top from 0 - menu.top = 0; - } else if (menu.top + visible >= files.count) { - menu.top = (files.count - 1) - visible; - } - - show_menu(&menu, rend, &files); + if (NULL != state) menu = *state; - int buttons = wait_for_input(input, &sys->input); - int special = (buttons >> 8); + int status = run_menu(&menu, &files, 20, + rend, input, sys); - if ( input_Result_Quit == special || - (buttons & (1 << Button_B))) { - // Cancel - menu.cursor = -1; - break; + if (input_Result_Quit == status) { + cart_filename = (char*)-1; - } else if (buttons & ( (1 << Button_A) | - (1 << Button_Start) )) { - // Select - break; + } else if (input_Result_Cancel == status) { + cart_filename = NULL; - } else if (buttons & (1 << Button_Up)) { - if (menu.cursor > 0) --menu.cursor; - - } else if (buttons & ( (1 << Button_Down) | - (1 << Button_Select) )) { - if (menu.cursor < (files.count - 1)) { - ++menu.cursor; - } - } - } - - // Selection has been made (or cancelled) - if (menu.cursor >= 0 && menu.cursor < files.count) { + } else if ( menu.cursor >= 0 && + menu.cursor < files.count) { char filename[1024]; snprintf(filename, sizeof(filename) - 1, "%s/%s", "rom", files.files[menu.cursor]); @@ -199,15 +228,48 @@ char* run_main_menu(menu_state* state, nes_Renderer* rend, free_file_list(&files); - if (NULL != state) { - *state = menu; - if (menu.cursor < 0) state->cursor = 0; - } + if (NULL != state) *state = menu; } return cart_filename; } +int run_game_menu(menu_state* state, nes_Renderer* rend, + nes_Input_Reader* input, nes* sys) { + static char* items[] = { + "Resume", + "Save", + "Load", + "Reset", + "Select ROM", + "Exit", + }; + static int choices[] = { + input_Result_OK, + input_Result_Save, + input_Result_Load, + input_Result_Reset, + input_Result_Menu, + input_Result_Quit, + }; + static const file_list options = { + .files = items, + .count = (sizeof(items) / sizeof(*items)), + }; + + menu_state menu = {0}; + if (NULL != state) menu = *state; + + int status = run_menu(&menu, &options, 100, + rend, input, sys); + + if (NULL != state) *state = menu; + + if (0 == status) status = choices[menu.cursor]; + + return status; +} + int modal_popup(const char* message, nes_Renderer* rend, nes_Input_Reader* input, nes* sys) { int w = 0; @@ -218,7 +280,7 @@ int modal_popup(const char* message, nes_Renderer* rend, int x = (nes_ppu_render_w - w) / 2; int y = (nes_ppu_render_h - h) / 2; - nes_draw_last_frame(rend); + nes_draw_last_frame(rend, NULL != sys->cart.mapper); nes_draw_text(rend, message, x, y); nes_draw_done(rend); diff --git a/src/sdl_render.c b/src/sdl_render.c index a2a7545..ae5351e 100644 --- a/src/sdl_render.c +++ b/src/sdl_render.c @@ -148,7 +148,7 @@ static int sdl_render_init(nes_Renderer* rend) { } else { SDL_LockTextureToSurface(data->texture, NULL, &data->target); - SDL_FillRect(data->target, NULL, 0x556677FF); + SDL_FillRect(data->target, NULL, 0x006677FF); } } @@ -560,11 +560,24 @@ static int sdl_render(nes_Renderer* rend, nes_ppu* ppu) { } -static void sdl_redraw_frame(nes_Renderer* rend) { +static void sdl_redraw_frame(nes_Renderer* rend, int dim) { sdl_render_data* data = (sdl_render_data*)rend->data; + + if (dim) { + SDL_SetTextureAlphaMod(data->texture, 0x7F); + SDL_SetTextureBlendMode(data->texture, + SDL_BLENDMODE_BLEND); + SDL_RenderClear(data->renderer); + } + SDL_UnlockTexture(data->texture); SDL_RenderCopy(data->renderer, data->texture, NULL, NULL); SDL_LockTextureToSurface(data->texture, NULL, &data->target); + + if (dim) { + SDL_SetTextureBlendMode(data->texture, + SDL_BLENDMODE_NONE); + } } static void sdl_draw_present(nes_Renderer* rend) {