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.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

397 line
11KB

  1. #include <inttypes.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include "nes.h"
  5. #include "timer.h"
  6. #include "render.h"
  7. #include "input.h"
  8. #include "audio.h"
  9. #include "mapper.h"
  10. #include "save.h"
  11. #include "menu.h"
  12. #include "state.h"
  13. #define audio_freq (44100U)
  14. extern nes_Renderer sdl_renderer;
  15. extern nes_Input_Reader sdl_input;
  16. extern nes_Audio_Stream sdl_audio;
  17. static FILE* nes_load_cart(nes_cart* cart,
  18. const char* cart_filename) {
  19. int status = 0;
  20. FILE* cart_file = fopen(cart_filename, "rb");
  21. if (NULL == cart_file) {
  22. status = -1;
  23. fprintf(stderr, "Could not open %s\n", cart_filename);
  24. }
  25. if (status == 0) {
  26. status = nes_cart_init_file(cart, cart_file);
  27. }
  28. if (status == 0) {
  29. // Failure might mean there's nothing to load
  30. load_sram(cart, cart_filename);
  31. }
  32. if (status != 0 && NULL != cart_file) {
  33. fclose(cart_file);
  34. cart_file = NULL;
  35. }
  36. return cart_file;
  37. }
  38. typedef struct {
  39. Overlay* overlay;
  40. int timer;
  41. int id;
  42. int mode;
  43. } loadsave_state;
  44. static void loadsave_start(loadsave_state* loadsave, int mode) {
  45. if (0 != loadsave->mode) {
  46. // We can't do two things at once.
  47. overlay_clear_message(loadsave->overlay, loadsave->id);
  48. loadsave->mode = 0;
  49. } else {
  50. loadsave->mode = mode;
  51. loadsave->timer = 60 * 1;
  52. const char* op = NULL;
  53. if (input_Result_Load == mode) op = "Restoring";
  54. else if (input_Result_Save == mode) op = "Saving";
  55. if (NULL != op) {
  56. char msg[100];
  57. snprintf(msg, sizeof(msg), "%s game state ...", op);
  58. loadsave->id = overlay_add_message(
  59. loadsave->overlay, msg, 60 * 1
  60. );
  61. }
  62. }
  63. }
  64. static int loadsave_tick(loadsave_state* loadsave) {
  65. int action = 0;
  66. if (loadsave->mode != 0 && --loadsave->timer <= 0) {
  67. const char *op = NULL;
  68. if (input_Result_Load == loadsave->mode) {
  69. op = "restored";
  70. } else if (input_Result_Save == loadsave->mode) {
  71. op = "saved";
  72. }
  73. if (NULL != op) {
  74. char msg[100];
  75. snprintf(msg, sizeof(msg), "Game state %s", op);
  76. overlay_add_message(loadsave->overlay, msg, 60 * 3);
  77. }
  78. overlay_clear_message(loadsave->overlay, loadsave->id);
  79. action = loadsave->mode;
  80. loadsave->mode = 0;
  81. }
  82. return action;
  83. }
  84. static int select_rom(menu_state* menu,
  85. nese_Components* comp,
  86. cart_info* cur_cart) {
  87. int status = 0;
  88. cart_info cart = {0};
  89. cart_info ref_cart = {
  90. .filename = cur_cart->filename,
  91. .file = cur_cart->file,
  92. };
  93. nes_cart new_cart = {0};
  94. while (0 == status && NULL == cart.file) {
  95. // Display a load failure message
  96. if (NULL != cart.filename) {
  97. char message[1024];
  98. snprintf(message, sizeof(message) - 1,
  99. "Could not load\n%s", cart.filename + 4);
  100. int button = modal_popup(message, comp, cur_cart);
  101. if (input_Result_Quit == (button >> 8)) {
  102. // Program closed inside modal
  103. status = input_Result_Quit;
  104. }
  105. }
  106. if (0 == status) {
  107. char* new_filename = run_main_menu(menu, comp,
  108. &ref_cart);
  109. if ((char*)-1 == new_filename) {
  110. status = input_Result_Quit;
  111. } else if (NULL == new_filename) {
  112. status = input_Result_Cancel;
  113. } else {
  114. free(cart.filename);
  115. cart.filename = new_filename;
  116. ref_cart.filename = new_filename;
  117. cart.file = nes_load_cart(&new_cart,
  118. cart.filename);
  119. }
  120. }
  121. }
  122. if (0 == status && NULL != cart.file) {
  123. save_sram(&comp->sys->cart, cur_cart->filename);
  124. nes_cart_done(&comp->sys->cart);
  125. cart_info_done(cur_cart);
  126. comp->sys->cart = new_cart;
  127. *cur_cart = cart;
  128. nes_setup_cart(comp->sys);
  129. } else {
  130. free(cart.filename);
  131. }
  132. return status;
  133. }
  134. static int do_game_menu(menu_state* menu,
  135. nese_Components* comp,
  136. nese_State* state) {
  137. int status = 0;
  138. menu_state rom_menu = {0};
  139. while (1) {
  140. status = run_game_menu(menu, comp, state);
  141. if ( input_Result_View == status ||
  142. input_Result_Scale == status ||
  143. input_Result_Effect == status ) {
  144. if (input_Result_View == status) {
  145. #ifndef STANDALONE
  146. state->flags ^= (1 << State_Bit_Fullscreen);
  147. #endif
  148. } else if (input_Result_Scale == status) {
  149. state->flags ^= (1 << State_Bit_Integer_Scale);
  150. } else {
  151. state->flags ^= (1 << State_Bit_CRT_Effect);
  152. }
  153. nes_render_set_flags(comp->rend, state->flags);
  154. continue;
  155. } else if (input_Result_Menu == status) {
  156. status = select_rom(&rom_menu, comp, &state->cart);
  157. if (input_Result_Cancel == status) {
  158. status = input_Result_OK;
  159. continue;
  160. }
  161. }
  162. break;
  163. }
  164. return status;
  165. }
  166. int main(int argc, char* argv[]) {
  167. int status = 0;
  168. nese_State state = {0};
  169. load_prefs_filename(&state, "nese.prefs");
  170. #ifdef STANDALONE
  171. state.flags |= (1 << State_Bit_Fullscreen);
  172. #endif
  173. nes sys = {0};
  174. if (status == 0) {
  175. status = nes_init(&sys, audio_freq);
  176. }
  177. nese_Components components = {
  178. .rend = &sdl_renderer,
  179. .reader = &sdl_input,
  180. .audio = &sdl_audio,
  181. .sys = &sys,
  182. };
  183. if (status == 0) {
  184. status = nes_render_init(components.rend);
  185. if (0 == status) {
  186. nes_render_set_flags( components.rend, state.flags);
  187. }
  188. }
  189. if (status == 0) {
  190. status = nes_input_init(components.reader);
  191. }
  192. if (status == 0) {
  193. status = nes_audio_init(components.audio, audio_freq);
  194. }
  195. if (0 == status && argc > 1) {
  196. cart_info cart = {
  197. .filename = strdup(argv[1]),
  198. };
  199. cart.file = nes_load_cart(&sys.cart, cart.filename);
  200. if (NULL != cart.file) {
  201. cart_info_done(&state.cart);
  202. state.cart = cart;
  203. nes_setup_cart(&sys);
  204. } else {
  205. cart_info_done(&cart);
  206. }
  207. }
  208. // If we didn't launch with a file, run the loader
  209. if (0 == status && NULL == state.cart.file) {
  210. status = select_rom(NULL, &components, &state.cart);
  211. }
  212. if (status == 0) {
  213. menu_state game_menu = {0};
  214. loadsave_state loadsave = {
  215. .overlay = &components.rend->overlay,
  216. };
  217. nes_render(components.rend, &sys.ppu);
  218. time_us t_target = time_now();
  219. uint64_t cycle_last_frame = 0;
  220. uint64_t total_cycles = 0;
  221. while (0 == status) {
  222. int run = 0;
  223. nes_ppu_Result result = nes_step(&sys, &run);
  224. total_cycles += run;
  225. if ( result == ppu_Result_Ready ||
  226. result == ppu_Result_VBlank_Off) {
  227. status = nes_render(components.rend, &sys.ppu);
  228. if (status > 0) {
  229. // Load/Save Operations
  230. int action = loadsave_tick(&loadsave);
  231. if (input_Result_Load == action) {
  232. load_state(&sys, state.cart.filename);
  233. } else if (input_Result_Save == action) {
  234. save_state(&sys, state.cart.filename);
  235. }
  236. // Sleep to catch up to master clock
  237. uint64_t elapsed_cycles = total_cycles -
  238. cycle_last_frame;
  239. int elapsed_us = ( elapsed_cycles *
  240. nes_clock_master_den *
  241. US_PER_S ) /
  242. nes_clock_master_num;
  243. t_target += elapsed_us;
  244. time_us slept_us = time_sleep_until(t_target);
  245. if (slept_us <= -elapsed_us) {
  246. // We're way out of sync.
  247. t_target = time_now();
  248. }
  249. cycle_last_frame = total_cycles;
  250. // Update button states every rendered frame
  251. status = nes_input_update(components.reader,
  252. &components);
  253. if (input_Result_Refresh == status) {
  254. status = input_Result_OK;
  255. } if (input_Result_Menu == status) {
  256. status = do_game_menu(
  257. &game_menu, &components, &state
  258. );
  259. if ( input_Result_Load == status ||
  260. input_Result_Save == status) {
  261. loadsave.mode = status;
  262. loadsave.timer = 0;
  263. status = input_Result_OK;
  264. }
  265. // Allow other options to fall through
  266. }
  267. if (input_Result_Reset == status) {
  268. overlay_add_message(
  269. &components.rend->overlay,
  270. "Game reset",
  271. 60 * 3
  272. );
  273. nes_reset(&sys);
  274. status = 0;
  275. } else if (input_Result_Load == status) {
  276. loadsave_start(&loadsave, status);
  277. status = 0;
  278. } else if (input_Result_Save == status) {
  279. loadsave_start(&loadsave, status);
  280. status = 0;
  281. } else if (input_Result_Cancel == status) {
  282. overlay_clear_message(loadsave.overlay,
  283. loadsave.id);
  284. loadsave.mode = 0;
  285. status = 0;
  286. }
  287. if (status == 0) {
  288. // Update audio, too
  289. status = nes_audio_fill(
  290. components.audio, &sys.apu
  291. );
  292. }
  293. }
  294. } else if (result == ppu_Result_Halt) {
  295. status = -1;
  296. }
  297. }
  298. float ms_run = ( total_cycles * 1000. *
  299. nes_clock_master_den) /
  300. nes_clock_master_num;
  301. fprintf(stdout,
  302. "Ran %f ms, %"PRIu64" master cycles (%s)\n",
  303. ms_run, total_cycles,
  304. status == 0 ? "OK" : "Halted"
  305. );
  306. // Failure might mean there's nothing to save
  307. save_sram(&sys.cart, state.cart.filename);
  308. }
  309. save_prefs_filename(&state, "nese.prefs");
  310. nese_state_done(&state);
  311. nes_done(&sys);
  312. nes_audio_done(components.audio);
  313. nes_input_done(components.reader);
  314. nes_render_done(components.rend);
  315. return status;
  316. }