|
- #include <inttypes.h>
- #include <stdlib.h>
- #include <string.h>
-
- #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;
- }
|