#include #include #include #include "nes.h" #include "timer.h" #include "render.h" #include "input.h" #include "audio.h" #include "mapper.h" #include "save.h" #include "menu.h" #include "state.h" #define audio_freq (44100U) extern nes_Renderer sdl_renderer; extern nes_Input_Reader sdl_input; extern nes_Audio_Stream sdl_audio; 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 { Overlay* overlay; int timer; int id; int mode; } loadsave_state; 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); loadsave->mode = 0; } else { loadsave->mode = mode; loadsave->timer = 60 * 1; const char* op = NULL; if (input_Result_Load == mode) op = "Restoring"; else if (input_Result_Save == mode) op = "Saving"; if (NULL != op) { char msg[100]; snprintf(msg, sizeof(msg), "%s game state ...", op); loadsave->id = overlay_add_message( loadsave->overlay, msg, 60 * 1 ); } } } 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) { op = "restored"; } else if (input_Result_Save == loadsave->mode) { op = "saved"; } if (NULL != op) { char msg[100]; snprintf(msg, sizeof(msg), "Game state %s", op); overlay_add_message(loadsave->overlay, msg, 60 * 3); } overlay_clear_message(loadsave->overlay, loadsave->id); action = loadsave->mode; loadsave->mode = 0; } return action; } static int select_rom(menu_state* menu, nese_Components* comp, cart_info* cur_cart) { int status = 0; cart_info cart = {0}; cart_info ref_cart = { .filename = cur_cart->filename, .file = cur_cart->file, }; nes_cart new_cart = {0}; while (0 == status && NULL == cart.file) { // Display a load failure message if (NULL != cart.filename) { char message[1024]; snprintf(message, sizeof(message) - 1, "Could not load\n%s", cart.filename + 4); int button = modal_popup(message, comp, cur_cart); if (input_Result_Quit == (button >> 8)) { // Program closed inside modal status = input_Result_Quit; } } if (0 == status) { char* new_filename = run_main_menu(menu, comp, &ref_cart); if ((char*)-1 == new_filename) { status = input_Result_Quit; } else if (NULL == new_filename) { status = input_Result_Cancel; } else { free(cart.filename); cart.filename = new_filename; ref_cart.filename = new_filename; cart.file = nes_load_cart(&new_cart, cart.filename); } } } if (0 == status && NULL != cart.file) { save_sram(&comp->sys->cart, cur_cart->filename); nes_cart_done(&comp->sys->cart); cart_info_done(cur_cart); comp->sys->cart = new_cart; *cur_cart = cart; nes_setup_cart(comp->sys); } else { free(cart.filename); } return status; } static int do_game_menu(menu_state* menu, nese_Components* comp, nese_State* state) { int status = 0; menu_state rom_menu = {0}; while (1) { status = run_game_menu(menu, comp, state); if ( input_Result_View == status || input_Result_Scale == status || input_Result_Effect == status ) { if (input_Result_View == status) { #ifndef STANDALONE state->flags ^= (1 << State_Bit_Fullscreen); #endif } else if (input_Result_Scale == status) { state->flags ^= (1 << State_Bit_Integer_Scale); } else { state->flags ^= (1 << State_Bit_CRT_Effect); } nes_render_set_flags(comp->rend, state->flags); continue; } else if (input_Result_Menu == status) { status = select_rom(&rom_menu, comp, &state->cart); if (input_Result_Cancel == status) { status = input_Result_OK; continue; } } break; } return status; } int main(int argc, char* argv[]) { int status = 0; nese_State state = {0}; load_prefs_filename(&state, "nese.prefs"); #ifdef STANDALONE state.flags |= (1 << State_Bit_Fullscreen); #endif nes sys = {0}; if (status == 0) { status = nes_init(&sys, audio_freq); } nese_Components components = { .rend = &sdl_renderer, .reader = &sdl_input, .audio = &sdl_audio, .sys = &sys, }; if (status == 0) { status = nes_render_init(components.rend); if (0 == status) { nes_render_set_flags( components.rend, state.flags); } } if (status == 0) { status = nes_input_init(components.reader); } if (status == 0) { status = nes_audio_init(components.audio, audio_freq); } if (0 == status && argc > 1) { cart_info cart = { .filename = strdup(argv[1]), }; cart.file = nes_load_cart(&sys.cart, cart.filename); if (NULL != cart.file) { cart_info_done(&state.cart); state.cart = cart; nes_setup_cart(&sys); } else { cart_info_done(&cart); } } // If we didn't launch with a file, run the loader if (0 == status && NULL == state.cart.file) { status = select_rom(NULL, &components, &state.cart); } if (status == 0) { menu_state game_menu = {0}; loadsave_state loadsave = { .overlay = &components.rend->overlay, }; nes_render(components.rend, &sys.ppu); time_us t_target = time_now(); uint64_t cycle_last_frame = 0; uint64_t total_cycles = 0; while (0 == status) { int run = 0; nes_ppu_Result result = nes_step(&sys, &run); total_cycles += run; if ( result == ppu_Result_Ready || result == ppu_Result_VBlank_Off) { status = nes_render(components.rend, &sys.ppu); if (status > 0) { // Load/Save Operations int action = loadsave_tick(&loadsave); if (input_Result_Load == action) { load_state(&sys, state.cart.filename); } else if (input_Result_Save == action) { save_state(&sys, state.cart.filename); } // Sleep to catch up to master clock uint64_t elapsed_cycles = total_cycles - cycle_last_frame; int elapsed_us = ( elapsed_cycles * nes_clock_master_den * US_PER_S ) / nes_clock_master_num; t_target += elapsed_us; time_us slept_us = time_sleep_until(t_target); if (slept_us <= -elapsed_us) { // We're way out of sync. t_target = time_now(); } cycle_last_frame = total_cycles; // Update button states every rendered frame status = nes_input_update(components.reader, &components); if (input_Result_Refresh == status) { status = input_Result_OK; } if (input_Result_Menu == status) { status = do_game_menu( &game_menu, &components, &state ); if ( input_Result_Load == status || input_Result_Save == status) { loadsave.mode = status; loadsave.timer = 0; status = input_Result_OK; } // Allow other options to fall through } if (input_Result_Reset == status) { overlay_add_message( &components.rend->overlay, "Game reset", 60 * 3 ); nes_reset(&sys); status = 0; } else if (input_Result_Load == status) { loadsave_start(&loadsave, status); status = 0; } else if (input_Result_Save == status) { loadsave_start(&loadsave, status); status = 0; } else if (input_Result_Cancel == status) { overlay_clear_message(loadsave.overlay, loadsave.id); loadsave.mode = 0; status = 0; } if (status == 0) { // Update audio, too status = nes_audio_fill( components.audio, &sys.apu ); } } } else if (result == ppu_Result_Halt) { status = -1; } } float ms_run = ( total_cycles * 1000. * nes_clock_master_den) / nes_clock_master_num; fprintf(stdout, "Ran %f ms, %"PRIu64" master cycles (%s)\n", ms_run, total_cycles, status == 0 ? "OK" : "Halted" ); // Failure might mean there's nothing to save save_sram(&sys.cart, state.cart.filename); } save_prefs_filename(&state, "nese.prefs"); nese_state_done(&state); nes_done(&sys); nes_audio_done(components.audio); nes_input_done(components.reader); nes_render_done(components.rend); return status; }