NESe (pronounced "Nessie") is a NES emulator based on the e6502 emulator, also written in C with a focus on speed and portability for use on embedded platforms, especially ARM.
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

669 líneas
17KB

  1. #include <errno.h>
  2. #include <stdio.h>
  3. #include <time.h>
  4. #include <unistd.h>
  5. #include <dirent.h>
  6. #include <sys/mman.h>
  7. #include <sys/stat.h>
  8. #include "action.h"
  9. #include "cart.h"
  10. #include "nese.h"
  11. #include "overlay.h"
  12. #include "port.h"
  13. #include "save.h"
  14. #include "draw.h"
  15. #include "cartinfo.h"
  16. #include "menu.h"
  17. #define NESE_DEBUG "Port"
  18. #include "log.h"
  19. //#define NESE_TURBO
  20. /*
  21. * OS-specific file operations
  22. * Memory mapping specifically needs to be ported for each OS
  23. */
  24. void* nese_map_file(FILE* file, int size, Filemap_Mode map_mode) {
  25. int prot = ( Filemap_Mode_Write == map_mode ?
  26. PROT_WRITE : PROT_READ);
  27. int flags = ( Filemap_Mode_Write == map_mode ?
  28. MAP_SHARED : MAP_PRIVATE);
  29. void* mem = mmap(NULL, size, prot, flags, fileno(file), 0);
  30. if ((void*)-1 == mem) {
  31. fprintf(stderr, "Failed to map file: %d\n", errno);
  32. mem = NULL;
  33. }
  34. return mem;
  35. }
  36. void nese_unmap_file(void* mem, int size) {
  37. munmap(mem, size);
  38. }
  39. int nese_mkdir(const char* dir) {
  40. return mkdir(dir, 0777);
  41. }
  42. int nese_file_size(FILE* file) {
  43. int size = -1;
  44. if (0 == fseek(file, 0, SEEK_END)) {
  45. size = ftell(file);
  46. }
  47. return size;
  48. }
  49. /*
  50. * Platform-specific allocation
  51. * GPU and CPU refer to emulator memory spaces
  52. * Note: GPU (and possibly CPU) regions may need
  53. * to be placed into internal ram for performance
  54. */
  55. void* nese_alloc_gpu(int size) {
  56. return calloc(1, size);
  57. }
  58. void* nese_alloc_cpu(int size) {
  59. return calloc(1, size);
  60. }
  61. void* nese_alloc(int size) {
  62. return calloc(1, size);
  63. }
  64. /*
  65. * Platform-specific features and controls
  66. */
  67. #define PLAT_FILENAME_SIZE (1024U)
  68. typedef enum {
  69. Flag_Turbo = 0b1,
  70. } Platform_Flags;
  71. typedef struct {
  72. nes* sys;
  73. Cart_Info cart;
  74. int64_t t_target;
  75. Render_Info renderer;
  76. Overlay overlay;
  77. int fps_msg_id;
  78. Platform_Flags flags;
  79. char filename[PLAT_FILENAME_SIZE];
  80. } platform_data;
  81. /* Directories */
  82. static int cmp_files(const void* _a, const void* _b) {
  83. const char* a = *(const char**)_a;
  84. const char* b = *(const char**)_b;
  85. int diff = 0;
  86. for (char ca = 1, cb = 1; ca && cb && 0 == diff; ++a, ++b) {
  87. // Cut extensions; replace underscore with space
  88. ca = (*a == '_' ? ' ' : (*a == '.' ? '\0' : *a));
  89. cb = (*b == '_' ? ' ' : (*b == '.' ? '\0' : *b));
  90. diff = (ca - cb);
  91. }
  92. return diff;
  93. }
  94. static int count_files(DIR* dir) {
  95. int count = 0;
  96. struct dirent* de = NULL;
  97. while (NULL != (de = readdir(dir))) {
  98. if ('.' != de->d_name[0]) ++count;
  99. }
  100. rewinddir(dir);
  101. return count;
  102. }
  103. static void make_file_list(DIR* dir, File_List* files) {
  104. files->count = count_files(dir);
  105. files->files = calloc(files->count, sizeof(char*));
  106. struct dirent* de = NULL;
  107. int i_file = 0;
  108. while (NULL != (de = readdir(dir))) {
  109. if ('.' != de->d_name[0]) {
  110. files->files[i_file] = strdup(de->d_name);
  111. ++i_file;
  112. }
  113. }
  114. qsort(files->files, files->count, sizeof(char*), cmp_files);
  115. }
  116. static void free_file_list(File_List* files) {
  117. for (int i = 0; i < files->count; ++i) {
  118. free(files->files[i]);
  119. }
  120. free(files->files);
  121. }
  122. static int find_file(File_List* files, const char* filename) {
  123. int i = (files->count - 1);
  124. for ( ; i >= 0 &&
  125. (0 != strcmp(files->files[i], filename)); --i);
  126. return i;
  127. }
  128. /* Input */
  129. static const int sdl_action_keycodes[Action_Max] = {
  130. [Action_Menu] = SDLK_ESCAPE,
  131. [Action_Reset] = SDLK_BACKSPACE,
  132. [Action_Load] = SDLK_F2,
  133. [Action_Save] = SDLK_F1,
  134. [Action_FPS] = SDLK_f,
  135. [Action_Turbo] = SDLK_t,
  136. };
  137. static const int sdl_button_keycodes[2 * nes_controller_num_buttons] = {
  138. SDLK_a,
  139. SDLK_s,
  140. SDLK_q,
  141. SDLK_w,
  142. SDLK_UP,
  143. SDLK_DOWN,
  144. SDLK_LEFT,
  145. SDLK_RIGHT,
  146. SDLK_LCTRL,
  147. SDLK_LALT,
  148. SDLK_TAB,
  149. SDLK_RETURN,
  150. SDLK_KP_8,
  151. SDLK_KP_2,
  152. SDLK_KP_4,
  153. SDLK_KP_6,
  154. };
  155. static int keycode_index(int keycode, const int* codes, int n_codes) {
  156. int index = n_codes - 1;
  157. for ( ; index >= 0 && keycode != codes[index]; --index);
  158. return index;
  159. }
  160. static nese_Action process_events(nes* sys) {
  161. nese_Action action = Action_OK;
  162. nes_Input* input = &sys->core.memory.input;
  163. SDL_Event event = {0};
  164. while (Action_OK == action && 0 != SDL_PollEvent(&event)) {
  165. if (SDL_QUIT == event.type) {
  166. action = Action_Quit;
  167. } else if ( ( SDL_KEYDOWN == event.type ||
  168. SDL_KEYUP == event.type) &&
  169. 0 == event.key.repeat
  170. ) {
  171. int index = keycode_index(
  172. event.key.keysym.sym,
  173. sdl_button_keycodes,
  174. 2 * nes_controller_num_buttons
  175. );
  176. if (index >= 0) {
  177. index %= nes_controller_num_buttons;
  178. uint8_t mask = (1 << index);
  179. if (SDL_KEYDOWN == event.type) {
  180. input->gamepads[0].buttons |= mask;
  181. } else {
  182. input->gamepads[0].buttons &= ~mask;
  183. }
  184. } else if (SDL_KEYDOWN == event.type) {
  185. index = keycode_index(
  186. event.key.keysym.sym,
  187. sdl_action_keycodes, Action_Max
  188. );
  189. if (index >= 0) action = index;
  190. }
  191. }
  192. // TODO: Controller inputs
  193. }
  194. return action;
  195. }
  196. /*
  197. * Time / Video - Should be maximally reusable across platforms
  198. */
  199. int nese_frame_start(void* plat_data, uint8_t background) {
  200. return render_frame_start( &((platform_data*)plat_data)->renderer,
  201. background);
  202. }
  203. int nese_line_ready(void* plat_data, uint8_t* buffer, int line) {
  204. /*
  205. platform_data* plat = (platform_data*)plat_data;
  206. SDL_Rect rect = {
  207. .x = 0,
  208. .y = line,
  209. .w = nes_ppu_render_w,
  210. .h = 1,
  211. };
  212. SDL_BlitSurface(plat->scanline, NULL, plat->target, &rect);
  213. */
  214. return 0;
  215. }
  216. #define NS_PER_S (1000L * 1000L * 1000L)
  217. #define FRAME_TIME_NS (16639267L)
  218. uint64_t time_now(void) {
  219. struct timespec ts_now = {0};
  220. clock_gettime(CLOCK_REALTIME, &ts_now);
  221. return (ts_now.tv_sec * NS_PER_S) + ts_now.tv_nsec;
  222. }
  223. int64_t time_sleep_until(int64_t t_target) {
  224. int64_t t_now = time_now();
  225. int64_t t_diff = (t_target - t_now) / 1000;
  226. if (t_diff > 0) {
  227. usleep(t_diff);
  228. }
  229. return t_diff * 1000;
  230. }
  231. static nese_Action game_menu(platform_data* plat);
  232. int nese_frame_ready(void* plat_data) {
  233. platform_data* plat = (platform_data*)plat_data;
  234. /*
  235. uint8_t* ptr = &plat->sys->core.memory.ppu.vram[0x3C0];
  236. for (int y = 0; y < 16; y += 2) {
  237. printf("%04lX", 0x2000 + ptr - plat->sys->core.memory.ppu.vram);
  238. for (int x = 0; x < 8; ++x) {
  239. printf(" %d %d", (*ptr >> 0) & 3, (*ptr >> 2) & 3);
  240. ++ptr;
  241. }
  242. ptr -= 8;
  243. fputs("\n ", stdout);
  244. for (int x = 0; x < 8; ++x) {
  245. printf(" %d %d", (*ptr >> 4) & 3, (*ptr >> 6) & 3);
  246. ++ptr;
  247. }
  248. putc('\n', stdout);
  249. }
  250. putc('\n', stdout);
  251. */
  252. int status = render_frame(&plat->renderer);
  253. if (0 == status) {
  254. overlay_render(&plat->overlay,
  255. nes_ppu_render_w, nes_ppu_render_h,
  256. &plat->renderer.view, plat->renderer.renderer);
  257. }
  258. if (0 == status) {
  259. status = render_frame_end(&plat->renderer);
  260. }
  261. // TODO: Check status first?
  262. nese_Action action = process_events(plat->sys);
  263. switch (action) {
  264. case Action_Quit:
  265. // TODO: Save SRAM
  266. status = -1;
  267. break;
  268. case Action_Save:
  269. status = save_state(plat->sys, plat->cart.filename);
  270. break;
  271. case Action_Load:
  272. status = load_state(plat->sys, plat->cart.filename);
  273. break;
  274. case Action_FPS:
  275. if (plat->fps_msg_id <= 0) {
  276. plat->fps_msg_id = overlay_add_message(&plat->overlay, "", 0);
  277. } else {
  278. overlay_clear_message(&plat->overlay, plat->fps_msg_id);
  279. plat->fps_msg_id = 0;
  280. }
  281. break;
  282. case Action_Turbo:
  283. plat->flags ^= Flag_Turbo;
  284. break;
  285. case Action_Menu:
  286. action = game_menu(plat);
  287. if (Action_Reset == action) {
  288. nes_reset(plat->sys);
  289. } else if (Action_Save == action) {
  290. status = save_state(plat->sys, plat->cart.filename);
  291. } else if (Action_Load == action) {
  292. status = load_state(plat->sys, plat->cart.filename);
  293. } else if (Action_Quit == action || NULL == plat->sys->cart_header) {
  294. status = -1;
  295. }
  296. // TODO: Other Actions?
  297. break;
  298. default:
  299. }
  300. // TODO: Perform more actions?
  301. if (0 == status) {
  302. plat->t_target += FRAME_TIME_NS;
  303. int64_t slept_ns = 0;
  304. if (plat->flags & Flag_Turbo) {
  305. int64_t now = time_now();
  306. slept_ns = plat->t_target - now;
  307. plat->t_target = now;
  308. } else {
  309. slept_ns = time_sleep_until(plat->t_target);
  310. if (slept_ns <= -FRAME_TIME_NS) {
  311. // We're way out of sync.
  312. plat->t_target = time_now();
  313. printf("Out of sync: %d\n", (int)slept_ns);
  314. }
  315. }
  316. static int frame = 0;
  317. static int64_t slept_total = 0;
  318. slept_total += slept_ns;
  319. if (60 == ++frame) {
  320. if (plat->fps_msg_id > 0) {
  321. int64_t game_elapsed = frame * FRAME_TIME_NS;
  322. int64_t cpu_elapsed = game_elapsed - slept_total;
  323. float fps = (double)(NS_PER_S * frame) / cpu_elapsed;
  324. char message[32];
  325. snprintf(message, sizeof(message), "%.1f fps", fps);
  326. overlay_update_message(&plat->overlay, plat->fps_msg_id, message);
  327. }
  328. slept_total = 0;
  329. frame = 0;
  330. }
  331. }
  332. return status;
  333. }
  334. /* Drawing */
  335. void nese_draw_begin(void* plat_data) {
  336. platform_data* plat = (platform_data*)plat_data;
  337. draw_begin(&plat->renderer, NULL != plat->sys->cart_header);
  338. }
  339. void nese_draw_text(void* plat_data, const char* str, int x, int y, uint32_t c) {
  340. draw_text(&((platform_data*)plat_data)->renderer,
  341. &((platform_data*)plat_data)->overlay.font,
  342. str, x, y, c);
  343. }
  344. void nese_text_size(void* plat_data, const char* str, int* w, int* h) {
  345. overlay_font_measure(&((platform_data*)plat_data)->overlay.font, str, w, h);
  346. }
  347. void nese_draw_finish(void* plat_data) {
  348. render_frame_end(&((platform_data*)plat_data)->renderer);
  349. }
  350. /* Audio */
  351. int nese_get_audio_frequency(void*) {
  352. return 44100;
  353. }
  354. // TODO: Audio functions
  355. /* Menus */
  356. nese_Action nese_update_input(void* plat_data, nes_Input* input) {
  357. time_sleep_until(time_now() + (NS_PER_S / 60));
  358. platform_data* plat = (platform_data*)plat_data;
  359. nese_Action action = process_events(plat->sys);
  360. if (NULL != input) *input = plat->sys->core.memory.input;
  361. return action;
  362. }
  363. static nese_Action run_rom_menu(platform_data* plat, Menu_State* state,
  364. char* filename, int sz_filename) {
  365. nese_Action action = Action_Cancel;
  366. DIR* dir = opendir("rom");
  367. if (NULL == dir) {
  368. modal_popup(plat, "No ROMS found!\nPress any key to exit",
  369. color_error);
  370. } else {
  371. File_List files = {0};
  372. make_file_list(dir, &files);
  373. closedir(dir);
  374. Menu_State menu = {0};
  375. if (NULL != state) menu = *state;
  376. // Add 4 to skip past "rom/"
  377. if (strlen(filename) > 4) {
  378. int current = find_file(&files, filename + 4);
  379. if (current >= 0) menu.cursor = current;
  380. }
  381. action = run_menu(plat, &menu, &files, 20);
  382. if (Action_OK == action) {
  383. if ( menu.cursor >= 0 &&
  384. menu.cursor < files.count) {
  385. snprintf(filename, sz_filename - 1,
  386. "%s/%s", "rom", files.files[menu.cursor]);
  387. } else {
  388. action = Action_Cancel;
  389. }
  390. }
  391. free_file_list(&files);
  392. if (NULL != state) *state = menu;
  393. }
  394. return action;
  395. }
  396. static nese_Action nese_load_cart(platform_data* plat, const char* filename) {
  397. nese_Action action = Action_OK;
  398. unload_cart(&plat->cart);
  399. plat->sys->cart_header = NULL;
  400. int status = load_cart(&plat->cart, filename, plat->sys);
  401. if (0 != status) {
  402. char message[1024];
  403. snprintf(message, sizeof(message), "Failed to load\n%s", filename);
  404. action = modal_popup(plat, message, color_error);
  405. if (Action_Quit != action) action = Action_Cancel;
  406. }
  407. return action;
  408. }
  409. static nese_Action select_rom(platform_data* plat, char* file, int sz_file) {
  410. nese_Action action = Action_OK;
  411. Menu_State menu = {0};
  412. // TODO: Save SRAM
  413. while (Action_OK == action || NULL == plat->sys->cart_header) {
  414. action = run_rom_menu(plat, &menu, file, sz_file);
  415. if (Action_OK == action) {
  416. plat->sys->cart_header = NULL;
  417. action = nese_load_cart(plat, file);
  418. if (Action_OK == action) break;
  419. // Failure - Now no ROM is currently loaded
  420. render_clear(&plat->renderer);
  421. file[0] = '\0';
  422. if (Action_Quit != action) action = Action_OK;
  423. } else if ( ( Action_Cancel == action &&
  424. NULL == plat->sys->cart_header) ||
  425. Action_Quit == action) {
  426. // If no cart is loaded, or the user tried to quit, just leave
  427. break;
  428. }
  429. }
  430. return action;
  431. }
  432. static nese_Action run_game_menu(platform_data* plat, Menu_State* state) {
  433. static char* items[] = {
  434. "Resume",
  435. "Save",
  436. "Restore",
  437. "Reset",
  438. "Select ROM",
  439. "Exit",
  440. };
  441. static const nese_Action choices[] = {
  442. Action_Cancel,
  443. Action_Save,
  444. Action_Load,
  445. Action_Reset,
  446. Action_Menu,
  447. Action_Quit,
  448. };
  449. static const File_List options = {
  450. .files = items,
  451. .count = (sizeof(items) / sizeof(*items)),
  452. };
  453. Menu_State menu = {0};
  454. if (NULL != state) menu = *state;
  455. nese_Action action = run_menu(plat, &menu, &options, 100);
  456. if (Action_Menu == action) {
  457. action = Action_Cancel;
  458. } else if (Action_OK == action) {
  459. if ( menu.cursor >= 0 &&
  460. menu.cursor < (sizeof(choices) / sizeof(*choices))) {
  461. action = choices[menu.cursor];
  462. }
  463. }
  464. if (NULL != state) *state = menu;
  465. return action;
  466. }
  467. static nese_Action game_menu(platform_data* plat) {
  468. nese_Action action = Action_OK;
  469. Menu_State menu = {0};
  470. while (Action_OK == action) {
  471. action = run_game_menu(plat, &menu);
  472. if (Action_Menu == action) {
  473. // Select ROM
  474. action = select_rom(plat, plat->filename, PLAT_FILENAME_SIZE);
  475. if (Action_OK == action) {
  476. // New ROM selected - Exit loop and reset
  477. action = Action_Reset;
  478. } else if (Action_Cancel == action) {
  479. // No ROM selected - Keep the menu running
  480. action = Action_OK;
  481. }
  482. if (NULL == plat->sys->cart_header) {
  483. // A failed ROM load means we shouldn't return to the game menu
  484. action = Action_Quit;
  485. }
  486. }
  487. }
  488. return action;
  489. }
  490. /* Platform Data */
  491. static int plat_init(platform_data* plat) {
  492. int status = render_info_init(&plat->renderer, plat->sys);
  493. if (0 == status) {
  494. status = overlay_init(&plat->overlay, plat->renderer.renderer);
  495. }
  496. if (0 == status) {
  497. plat->t_target = time_now();
  498. }
  499. return status;
  500. }
  501. static void plat_done(platform_data* plat) {
  502. render_info_done(&plat->renderer);
  503. overlay_done(&plat->overlay);
  504. SDL_Quit();
  505. }
  506. // This is too big for the stack.
  507. static nes sys = {0};
  508. int main(int argc, char* argv[]) {
  509. int status = 0;
  510. // This should be tiny enough to keep on the stack.
  511. platform_data plat = {
  512. .sys = &sys,
  513. };
  514. if (0 == status) {
  515. status = plat_init(&plat);
  516. }
  517. if (0 == status) {
  518. if (1 < argc) {
  519. strncpy(plat.filename, argv[1], PLAT_FILENAME_SIZE - 1);
  520. plat.filename[PLAT_FILENAME_SIZE - 1] = '\0';
  521. nese_Action action = nese_load_cart(&plat, plat.filename);
  522. status = (Action_OK == action) ? 0 : -1;
  523. }
  524. if (NULL == sys.cart_header) {
  525. nese_Action action = select_rom(
  526. &plat, plat.filename, PLAT_FILENAME_SIZE
  527. );
  528. status = (Action_OK == action) ? 0 : -1;
  529. }
  530. }
  531. // This shall invoke menus as needed
  532. if (0 == status) {
  533. status = nese_start(&sys, &plat);
  534. }
  535. unload_cart(&plat.cart);
  536. plat_done(&plat);
  537. return status;
  538. }