|
- #include <errno.h>
- #include <stdio.h>
-
- #include <time.h>
- #include <unistd.h>
- #include <dirent.h>
-
- #include <sys/mman.h>
- #include <sys/stat.h>
-
- #include "action.h"
- #include "cart.h"
- #include "nese.h"
- #include "overlay.h"
- #include "port.h"
- #include "save.h"
- #include "draw.h"
- #include "cartinfo.h"
- #include "menu.h"
-
- #define NESE_DEBUG "Port"
- #include "log.h"
-
- //#define NESE_TURBO
-
-
- /*
- * OS-specific file operations
- * Memory mapping specifically needs to be ported for each OS
- */
-
- void* nese_map_file(FILE* file, int size, Filemap_Mode map_mode) {
- int prot = ( Filemap_Mode_Write == map_mode ?
- PROT_WRITE : PROT_READ);
- int flags = ( Filemap_Mode_Write == map_mode ?
- MAP_SHARED : MAP_PRIVATE);
- void* mem = mmap(NULL, size, prot, flags, fileno(file), 0);
- if ((void*)-1 == mem) {
- fprintf(stderr, "Failed to map file: %d\n", errno);
- mem = NULL;
- }
- return mem;
- }
-
- void nese_unmap_file(void* mem, int size) {
- munmap(mem, size);
- }
-
- int nese_mkdir(const char* dir) {
- return mkdir(dir, 0777);
- }
-
- int nese_file_size(FILE* file) {
- int size = -1;
- if (0 == fseek(file, 0, SEEK_END)) {
- size = ftell(file);
- }
- return size;
- }
-
-
- /*
- * Platform-specific allocation
- * GPU and CPU refer to emulator memory spaces
- * Note: GPU (and possibly CPU) regions may need
- * to be placed into internal ram for performance
- */
-
- void* nese_alloc_gpu(int size) {
- return calloc(1, size);
- }
-
- void* nese_alloc_cpu(int size) {
- return calloc(1, size);
- }
-
- void* nese_alloc(int size) {
- return calloc(1, size);
- }
-
-
- /*
- * Platform-specific features and controls
- */
-
- #define PLAT_FILENAME_SIZE (1024U)
-
- typedef enum {
- Flag_Turbo = 0b1,
- } Platform_Flags;
-
- typedef struct {
- nes* sys;
- Cart_Info cart;
- int64_t t_target;
- Render_Info renderer;
- Overlay overlay;
- int fps_msg_id;
- Platform_Flags flags;
- char filename[PLAT_FILENAME_SIZE];
- } platform_data;
-
-
- /* Directories */
-
- static int cmp_files(const void* _a, const void* _b) {
- const char* a = *(const char**)_a;
- const char* b = *(const char**)_b;
-
- int diff = 0;
- for (char ca = 1, cb = 1; ca && cb && 0 == diff; ++a, ++b) {
- // Cut extensions; replace underscore with space
- ca = (*a == '_' ? ' ' : (*a == '.' ? '\0' : *a));
- cb = (*b == '_' ? ' ' : (*b == '.' ? '\0' : *b));
- diff = (ca - cb);
- }
-
- return diff;
- }
-
- static int count_files(DIR* dir) {
- int count = 0;
- struct dirent* de = NULL;
- while (NULL != (de = readdir(dir))) {
- if ('.' != de->d_name[0]) ++count;
- }
- rewinddir(dir);
- return count;
- }
-
- static void make_file_list(DIR* dir, File_List* files) {
- files->count = count_files(dir);
- files->files = calloc(files->count, sizeof(char*));
- struct dirent* de = NULL;
- int i_file = 0;
- while (NULL != (de = readdir(dir))) {
- if ('.' != de->d_name[0]) {
- files->files[i_file] = strdup(de->d_name);
- ++i_file;
- }
- }
- qsort(files->files, files->count, sizeof(char*), cmp_files);
- }
-
- static void free_file_list(File_List* files) {
- for (int i = 0; i < files->count; ++i) {
- free(files->files[i]);
- }
- free(files->files);
- }
-
- static int find_file(File_List* files, const char* filename) {
- int i = (files->count - 1);
- for ( ; i >= 0 &&
- (0 != strcmp(files->files[i], filename)); --i);
- return i;
- }
-
-
- /* Input */
-
- static const int sdl_action_keycodes[Action_Max] = {
- [Action_Menu] = SDLK_ESCAPE,
- [Action_Reset] = SDLK_BACKSPACE,
- [Action_Load] = SDLK_F2,
- [Action_Save] = SDLK_F1,
- [Action_FPS] = SDLK_f,
- [Action_Turbo] = SDLK_t,
- };
-
- static const int sdl_button_keycodes[2 * nes_controller_num_buttons] = {
- SDLK_a,
- SDLK_s,
- SDLK_q,
- SDLK_w,
- SDLK_UP,
- SDLK_DOWN,
- SDLK_LEFT,
- SDLK_RIGHT,
-
- SDLK_LCTRL,
- SDLK_LALT,
- SDLK_TAB,
- SDLK_RETURN,
- SDLK_KP_8,
- SDLK_KP_2,
- SDLK_KP_4,
- SDLK_KP_6,
- };
-
- static int keycode_index(int keycode, const int* codes, int n_codes) {
- int index = n_codes - 1;
- for ( ; index >= 0 && keycode != codes[index]; --index);
- return index;
- }
-
- static nese_Action process_events(nes* sys) {
- nese_Action action = Action_OK;
- nes_Input* input = &sys->core.memory.input;
-
- SDL_Event event = {0};
- while (Action_OK == action && 0 != SDL_PollEvent(&event)) {
- if (SDL_QUIT == event.type) {
- action = Action_Quit;
-
- } else if ( ( SDL_KEYDOWN == event.type ||
- SDL_KEYUP == event.type) &&
- 0 == event.key.repeat
- ) {
- int index = keycode_index(
- event.key.keysym.sym,
- sdl_button_keycodes,
- 2 * nes_controller_num_buttons
- );
- if (index >= 0) {
- index %= nes_controller_num_buttons;
- uint8_t mask = (1 << index);
- if (SDL_KEYDOWN == event.type) {
- input->gamepads[0].buttons |= mask;
- } else {
- input->gamepads[0].buttons &= ~mask;
- }
-
- } else if (SDL_KEYDOWN == event.type) {
- index = keycode_index(
- event.key.keysym.sym,
- sdl_action_keycodes, Action_Max
- );
- if (index >= 0) action = index;
- }
- }
- // TODO: Controller inputs
- }
-
- return action;
- }
-
-
- /*
- * Time / Video - Should be maximally reusable across platforms
- */
-
- int nese_frame_start(void* plat_data, uint8_t background) {
- return render_frame_start( &((platform_data*)plat_data)->renderer,
- background);
- }
-
- int nese_line_ready(void* plat_data, uint8_t* buffer, int line) {
- /*
- platform_data* plat = (platform_data*)plat_data;
-
- SDL_Rect rect = {
- .x = 0,
- .y = line,
- .w = nes_ppu_render_w,
- .h = 1,
- };
-
- SDL_BlitSurface(plat->scanline, NULL, plat->target, &rect);
- */
- return 0;
- }
-
-
- #define NS_PER_S (1000L * 1000L * 1000L)
- #define FRAME_TIME_NS (16639267L)
-
- uint64_t time_now(void) {
- struct timespec ts_now = {0};
- clock_gettime(CLOCK_REALTIME, &ts_now);
- return (ts_now.tv_sec * NS_PER_S) + ts_now.tv_nsec;
- }
-
- int64_t time_sleep_until(int64_t t_target) {
- int64_t t_now = time_now();
- int64_t t_diff = (t_target - t_now) / 1000;
- if (t_diff > 0) {
- usleep(t_diff);
- }
- return t_diff * 1000;
- }
-
- static nese_Action game_menu(platform_data* plat);
-
- int nese_frame_ready(void* plat_data) {
- platform_data* plat = (platform_data*)plat_data;
- /*
- uint8_t* ptr = &plat->sys->core.memory.ppu.vram[0x3C0];
- for (int y = 0; y < 16; y += 2) {
- printf("%04lX", 0x2000 + ptr - plat->sys->core.memory.ppu.vram);
- for (int x = 0; x < 8; ++x) {
- printf(" %d %d", (*ptr >> 0) & 3, (*ptr >> 2) & 3);
- ++ptr;
- }
- ptr -= 8;
- fputs("\n ", stdout);
- for (int x = 0; x < 8; ++x) {
- printf(" %d %d", (*ptr >> 4) & 3, (*ptr >> 6) & 3);
- ++ptr;
- }
- putc('\n', stdout);
- }
- putc('\n', stdout);
- */
- int status = render_frame(&plat->renderer);
-
- if (0 == status) {
- overlay_render(&plat->overlay,
- nes_ppu_render_w, nes_ppu_render_h,
- &plat->renderer.view, plat->renderer.renderer);
- }
-
- if (0 == status) {
- status = render_frame_end(&plat->renderer);
- }
-
-
- // TODO: Check status first?
-
- nese_Action action = process_events(plat->sys);
- switch (action) {
- case Action_Quit:
- // TODO: Save SRAM
- status = -1;
- break;
-
- case Action_Save:
- status = save_state(plat->sys, plat->cart.filename);
- break;
-
- case Action_Load:
- status = load_state(plat->sys, plat->cart.filename);
- break;
-
- case Action_FPS:
- if (plat->fps_msg_id <= 0) {
- plat->fps_msg_id = overlay_add_message(&plat->overlay, "", 0);
- } else {
- overlay_clear_message(&plat->overlay, plat->fps_msg_id);
- plat->fps_msg_id = 0;
- }
- break;
-
- case Action_Turbo:
- plat->flags ^= Flag_Turbo;
- break;
-
- case Action_Menu:
- action = game_menu(plat);
- if (Action_Reset == action) {
- nes_reset(plat->sys);
- } else if (Action_Save == action) {
- status = save_state(plat->sys, plat->cart.filename);
- } else if (Action_Load == action) {
- status = load_state(plat->sys, plat->cart.filename);
- } else if (Action_Quit == action || NULL == plat->sys->cart_header) {
- status = -1;
- }
- // TODO: Other Actions?
- break;
-
- default:
- }
-
- // TODO: Perform more actions?
-
- if (0 == status) {
- plat->t_target += FRAME_TIME_NS;
- int64_t slept_ns = 0;
- if (plat->flags & Flag_Turbo) {
- int64_t now = time_now();
- slept_ns = plat->t_target - now;
- plat->t_target = now;
- } else {
- slept_ns = time_sleep_until(plat->t_target);
- if (slept_ns <= -FRAME_TIME_NS) {
- // We're way out of sync.
- plat->t_target = time_now();
- printf("Out of sync: %d\n", (int)slept_ns);
- }
- }
-
- static int frame = 0;
- static int64_t slept_total = 0;
- slept_total += slept_ns;
- if (60 == ++frame) {
- if (plat->fps_msg_id > 0) {
- int64_t game_elapsed = frame * FRAME_TIME_NS;
- int64_t cpu_elapsed = game_elapsed - slept_total;
- float fps = (double)(NS_PER_S * frame) / cpu_elapsed;
-
- char message[32];
- snprintf(message, sizeof(message), "%.1f fps", fps);
- overlay_update_message(&plat->overlay, plat->fps_msg_id, message);
- }
-
- slept_total = 0;
- frame = 0;
- }
-
- }
-
- return status;
- }
-
-
- /* Drawing */
-
- void nese_draw_begin(void* plat_data) {
- platform_data* plat = (platform_data*)plat_data;
- draw_begin(&plat->renderer, NULL != plat->sys->cart_header);
- }
-
- void nese_draw_text(void* plat_data, const char* str, int x, int y, uint32_t c) {
- draw_text(&((platform_data*)plat_data)->renderer,
- &((platform_data*)plat_data)->overlay.font,
- str, x, y, c);
- }
-
- void nese_text_size(void* plat_data, const char* str, int* w, int* h) {
- overlay_font_measure(&((platform_data*)plat_data)->overlay.font, str, w, h);
- }
-
- void nese_draw_finish(void* plat_data) {
- render_frame_end(&((platform_data*)plat_data)->renderer);
- }
-
-
- /* Audio */
-
- int nese_get_audio_frequency(void*) {
- return 44100;
- }
-
- // TODO: Audio functions
-
-
- /* Menus */
-
- nese_Action nese_update_input(void* plat_data, nes_Input* input) {
- time_sleep_until(time_now() + (NS_PER_S / 60));
- platform_data* plat = (platform_data*)plat_data;
- nese_Action action = process_events(plat->sys);
- if (NULL != input) *input = plat->sys->core.memory.input;
- return action;
- }
-
- static nese_Action run_rom_menu(platform_data* plat, Menu_State* state,
- char* filename, int sz_filename) {
- nese_Action action = Action_Cancel;
-
- DIR* dir = opendir("rom");
-
- if (NULL == dir) {
- modal_popup(plat, "No ROMS found!\nPress any key to exit",
- color_error);
-
- } else {
- File_List files = {0};
- make_file_list(dir, &files);
- closedir(dir);
-
- Menu_State menu = {0};
- if (NULL != state) menu = *state;
-
- // Add 4 to skip past "rom/"
- if (strlen(filename) > 4) {
- int current = find_file(&files, filename + 4);
- if (current >= 0) menu.cursor = current;
- }
-
- action = run_menu(plat, &menu, &files, 20);
-
- if (Action_OK == action) {
- if ( menu.cursor >= 0 &&
- menu.cursor < files.count) {
- snprintf(filename, sz_filename - 1,
- "%s/%s", "rom", files.files[menu.cursor]);
- } else {
- action = Action_Cancel;
- }
- }
-
- free_file_list(&files);
-
- if (NULL != state) *state = menu;
- }
-
- return action;
- }
-
- static nese_Action nese_load_cart(platform_data* plat, const char* filename) {
- nese_Action action = Action_OK;
- unload_cart(&plat->cart);
- plat->sys->cart_header = NULL;
- int status = load_cart(&plat->cart, filename, plat->sys);
- if (0 != status) {
- char message[1024];
- snprintf(message, sizeof(message), "Failed to load\n%s", filename);
- action = modal_popup(plat, message, color_error);
- if (Action_Quit != action) action = Action_Cancel;
- }
- return action;
- }
-
- static nese_Action select_rom(platform_data* plat, char* file, int sz_file) {
- nese_Action action = Action_OK;
- Menu_State menu = {0};
-
- // TODO: Save SRAM
-
- while (Action_OK == action || NULL == plat->sys->cart_header) {
- action = run_rom_menu(plat, &menu, file, sz_file);
- if (Action_OK == action) {
- plat->sys->cart_header = NULL;
- action = nese_load_cart(plat, file);
- if (Action_OK == action) break;
- // Failure - Now no ROM is currently loaded
- render_clear(&plat->renderer);
- file[0] = '\0';
- if (Action_Quit != action) action = Action_OK;
- } else if ( ( Action_Cancel == action &&
- NULL == plat->sys->cart_header) ||
- Action_Quit == action) {
- // If no cart is loaded, or the user tried to quit, just leave
- break;
- }
- }
-
- return action;
- }
-
- static nese_Action run_game_menu(platform_data* plat, Menu_State* state) {
- static char* items[] = {
- "Resume",
- "Save",
- "Restore",
- "Reset",
- "Select ROM",
- "Exit",
- };
- static const nese_Action choices[] = {
- Action_Cancel,
- Action_Save,
- Action_Load,
- Action_Reset,
- Action_Menu,
- Action_Quit,
- };
- static const File_List options = {
- .files = items,
- .count = (sizeof(items) / sizeof(*items)),
- };
-
- Menu_State menu = {0};
- if (NULL != state) menu = *state;
-
- nese_Action action = run_menu(plat, &menu, &options, 100);
- if (Action_Menu == action) {
- action = Action_Cancel;
- } else if (Action_OK == action) {
- if ( menu.cursor >= 0 &&
- menu.cursor < (sizeof(choices) / sizeof(*choices))) {
- action = choices[menu.cursor];
- }
- }
-
- if (NULL != state) *state = menu;
-
- return action;
- }
-
- static nese_Action game_menu(platform_data* plat) {
- nese_Action action = Action_OK;
- Menu_State menu = {0};
-
- while (Action_OK == action) {
- action = run_game_menu(plat, &menu);
-
- if (Action_Menu == action) {
- // Select ROM
- action = select_rom(plat, plat->filename, PLAT_FILENAME_SIZE);
- if (Action_OK == action) {
- // New ROM selected - Exit loop and reset
- action = Action_Reset;
-
- } else if (Action_Cancel == action) {
- // No ROM selected - Keep the menu running
- action = Action_OK;
- }
-
- if (NULL == plat->sys->cart_header) {
- // A failed ROM load means we shouldn't return to the game menu
- action = Action_Quit;
- }
- }
- }
-
- return action;
- }
-
-
- /* Platform Data */
-
- static int plat_init(platform_data* plat) {
- int status = render_info_init(&plat->renderer, plat->sys);
-
- if (0 == status) {
- status = overlay_init(&plat->overlay, plat->renderer.renderer);
- }
-
- if (0 == status) {
- plat->t_target = time_now();
- }
-
- return status;
- }
-
- static void plat_done(platform_data* plat) {
- render_info_done(&plat->renderer);
- overlay_done(&plat->overlay);
- SDL_Quit();
- }
-
-
- // This is too big for the stack.
- static nes sys = {0};
-
-
- int main(int argc, char* argv[]) {
- int status = 0;
-
- // This should be tiny enough to keep on the stack.
- platform_data plat = {
- .sys = &sys,
- };
-
- if (0 == status) {
- status = plat_init(&plat);
- }
-
- if (0 == status) {
- if (1 < argc) {
- strncpy(plat.filename, argv[1], PLAT_FILENAME_SIZE - 1);
- plat.filename[PLAT_FILENAME_SIZE - 1] = '\0';
- nese_Action action = nese_load_cart(&plat, plat.filename);
- status = (Action_OK == action) ? 0 : -1;
- }
-
- if (NULL == sys.cart_header) {
- nese_Action action = select_rom(
- &plat, plat.filename, PLAT_FILENAME_SIZE
- );
- status = (Action_OK == action) ? 0 : -1;
- }
- }
-
- // This shall invoke menus as needed
- if (0 == status) {
- status = nese_start(&sys, &plat);
- }
-
- unload_cart(&plat.cart);
-
- plat_done(&plat);
-
- return status;
- }
|