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.
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

346 wiersze
9.3KB

  1. #include <stdlib.h>
  2. #include <string.h>
  3. #include <dirent.h>
  4. #include "menu.h"
  5. #include "file.h"
  6. #include "timer.h"
  7. static int get_input(nes_Input_Reader* reader,
  8. nes_input* input, int *last) {
  9. int status = nes_input_update(reader, input);
  10. int new_buttons = input->controllers[0].buttons;
  11. if (0 == status) {
  12. status = (~*last & new_buttons);
  13. } else {
  14. status = (status << 8);
  15. }
  16. *last = new_buttons;
  17. return status;
  18. }
  19. static int wait_for_input(nes_Input_Reader* reader,
  20. nes_input* input) {
  21. int buttons = input->controllers[0].buttons;
  22. int status = 0;
  23. for ( ;
  24. 0 == status;
  25. status = get_input(reader, input, &buttons) ) {
  26. time_sleep(US_PER_S / 60);
  27. }
  28. return status;
  29. }
  30. static int wait_for_input_quiet(nes_Input_Reader* reader,
  31. nes_input* input) {
  32. int status = 0;
  33. while ( input_Result_Quit != status &&
  34. input->controllers[0].buttons) {
  35. time_sleep(US_PER_S / 60);
  36. status = nes_input_update(reader, input);
  37. }
  38. return ( input_Result_Quit == status ?
  39. input_Result_Quit : 0);
  40. }
  41. static int count_files(DIR* dir) {
  42. int count = 0;
  43. struct dirent* de = NULL;
  44. while (NULL != (de = readdir(dir))) {
  45. if ('.' != de->d_name[0]) ++count;
  46. }
  47. rewinddir(dir);
  48. return count;
  49. }
  50. typedef struct {
  51. int count;
  52. char** files;
  53. } file_list;
  54. int cmp_files(const void* _a, const void* _b) {
  55. const char* a = *(const char**)_a;
  56. const char* b = *(const char**)_b;
  57. int diff = 0;
  58. for (char ca = 1, cb = 1; ca && cb && 0 == diff; ++a, ++b) {
  59. // Cut extensions; replace underscore with space
  60. ca = (*a == '_' ? ' ' : (*a == '.' ? '\0' : *a));
  61. cb = (*b == '_' ? ' ' : (*b == '.' ? '\0' : *b));
  62. diff = (ca - cb);
  63. }
  64. return diff;
  65. }
  66. static void make_file_list(DIR* dir, file_list* files) {
  67. files->count = count_files(dir);
  68. files->files = calloc(files->count, sizeof(char*));
  69. struct dirent* de = NULL;
  70. int i_file = 0;
  71. while (NULL != (de = readdir(dir))) {
  72. if ('.' != de->d_name[0]) {
  73. files->files[i_file] = strdup(de->d_name);
  74. ++i_file;
  75. }
  76. }
  77. qsort(files->files, files->count, sizeof(char*), cmp_files);
  78. }
  79. static void free_file_list(file_list* files) {
  80. for (int i = 0; i < files->count; ++i) {
  81. free(files->files[i]);
  82. }
  83. free(files->files);
  84. }
  85. static int find_file(file_list* files, const char* filename) {
  86. int i = (files->count - 1);
  87. for ( ; i >= 0 &&
  88. (0 != strcmp(files->files[i], filename)); --i);
  89. return i;
  90. }
  91. static void fix_filename(char* dst, int n, const char* src) {
  92. for ( int i = 0;
  93. i < n && *src && '.' != *src;
  94. ++i, ++dst, ++src) {
  95. *dst = ('_' == *src ? ' ' : *src);
  96. }
  97. *dst = '\0';
  98. }
  99. static inline int n_visible(void) {
  100. return ((nes_ppu_render_h - 20) / 11);
  101. }
  102. static const uint32_t menu_colors[6] = {
  103. color_red, color_orange, color_yellow,
  104. color_green, color_blue, color_purple,
  105. };
  106. static void show_menu(const menu_state* menu, int dim, int x,
  107. nes_Renderer* rend,
  108. const file_list* files) {
  109. nes_draw_last_frame(rend, dim);
  110. int bottom = menu->top + n_visible() - 1;
  111. int max = n_visible();
  112. if (max > files->count) max = files->count;
  113. int y = (nes_ppu_render_h - (max * 11)) / 2;
  114. for ( int n = menu->top;
  115. n < files->count && n <= bottom;
  116. ++n, y += 11 ) {
  117. char filename[100];
  118. fix_filename(filename, sizeof(filename) - 1,
  119. files->files[n]);
  120. nes_draw_text(
  121. rend,
  122. ( (menu->top == n && 0 != menu->top) ||
  123. (bottom == n && files->count - 1 > bottom) ) ?
  124. "..." : filename,
  125. x, y,
  126. (menu->cursor == n) ?
  127. color_white : menu_colors[n % 6]
  128. );
  129. if (menu->cursor == n) {
  130. nes_draw_text(rend, ">", x - 10, y,
  131. color_white);
  132. }
  133. }
  134. nes_draw_done(rend);
  135. }
  136. static int run_menu(menu_state* state, const file_list* files,
  137. int x, nes_Renderer* rend,
  138. nes_Input_Reader* input, nes* sys) {
  139. menu_state menu = {0};
  140. if (NULL != state) {
  141. menu = *state;
  142. if (menu.cursor < 0) {
  143. menu.cursor = 0;
  144. } else if (menu.cursor >= files->count) {
  145. menu.cursor = files->count - 1;
  146. }
  147. }
  148. int status = 0;
  149. while (0 == status) {
  150. // Scrolling (do this first to ensure menu is valid)
  151. const int visible = n_visible() - 1;
  152. menu.top = menu.cursor - (visible / 2);
  153. if (menu.top <= 0) {
  154. // We use <= so we don't readjust the top from 0
  155. menu.top = 0;
  156. } else if (menu.top + visible >= files->count) {
  157. menu.top = (files->count - 1) - visible;
  158. }
  159. show_menu(&menu, NULL != sys->cart.mapper,
  160. x, rend, files);
  161. int buttons = wait_for_input(input, &sys->input);
  162. int special = (buttons >> 8);
  163. if ( input_Result_Quit == special ||
  164. input_Result_Refresh == special) {
  165. status = special;
  166. } else if ( input_Result_Menu == special ||
  167. (buttons & (1 << Button_B))) {
  168. status = input_Result_Cancel;
  169. } else if (buttons & ( (1 << Button_A) |
  170. (1 << Button_Start) )) {
  171. // Select
  172. break;
  173. } else if (buttons & (1 << Button_Up)) {
  174. if (menu.cursor > 0) --menu.cursor;
  175. } else if (buttons & ( (1 << Button_Down) |
  176. (1 << Button_Select) )) {
  177. if (menu.cursor < (files->count - 1)) {
  178. ++menu.cursor;
  179. } else if (buttons & (1 << Button_Select)) {
  180. // Wrap around on Select
  181. menu.cursor = 0;
  182. }
  183. }
  184. }
  185. if (NULL != state) *state = menu;
  186. return status;
  187. }
  188. char* run_main_menu(menu_state* state, nes_Renderer* rend,
  189. nes_Input_Reader* input, nes* sys,
  190. const char* cur_filename) {
  191. char* cart_filename = NULL;
  192. DIR* dir = opendir("rom");
  193. if (NULL == dir) {
  194. nes_draw_last_frame(rend, NULL != sys->cart.mapper);
  195. nes_draw_text(
  196. rend,
  197. "No ROMS found!\nPress any key to exit",
  198. 10, 21, color_error
  199. );
  200. nes_draw_done(rend);
  201. wait_for_input(input, &sys->input);
  202. } else {
  203. file_list files = {0};
  204. make_file_list(dir, &files);
  205. closedir(dir);
  206. menu_state menu = {0};
  207. if (NULL != state) menu = *state;
  208. if (NULL != cur_filename) {
  209. // Add 4 to skip past "rom/"
  210. int current = find_file(&files, cur_filename + 4);
  211. if (current >= 0) menu.cursor = current;
  212. }
  213. // Don't let window refreshes interrupt us.
  214. int status = input_Result_Refresh;
  215. while (input_Result_Refresh == status) {
  216. status = run_menu(&menu, &files, 20,
  217. rend, input, sys);
  218. }
  219. if (input_Result_Quit == status) {
  220. cart_filename = (char*)-1;
  221. } else if (input_Result_Cancel == status) {
  222. cart_filename = NULL;
  223. } else if ( menu.cursor >= 0 &&
  224. menu.cursor < files.count) {
  225. char filename[1024];
  226. snprintf(filename, sizeof(filename) - 1,
  227. "%s/%s", "rom", files.files[menu.cursor]);
  228. cart_filename = strdup(filename);
  229. }
  230. free_file_list(&files);
  231. if (NULL != state) *state = menu;
  232. }
  233. return cart_filename;
  234. }
  235. int run_game_menu(menu_state* state, nes_Renderer* rend,
  236. nes_Input_Reader* input, nes* sys) {
  237. static char* items[] = {
  238. "Resume",
  239. "Save",
  240. "Restore",
  241. "Reset",
  242. "Select ROM",
  243. "Toggle Fullscreen",
  244. "Exit",
  245. };
  246. static int choices[] = {
  247. input_Result_OK,
  248. input_Result_Save,
  249. input_Result_Load,
  250. input_Result_Reset,
  251. input_Result_Menu,
  252. input_Result_View,
  253. input_Result_Quit,
  254. };
  255. static const file_list options = {
  256. .files = items,
  257. .count = (sizeof(items) / sizeof(*items)),
  258. };
  259. menu_state menu = {0};
  260. if (NULL != state) menu = *state;
  261. int status = run_menu(&menu, &options, 100,
  262. rend, input, sys);
  263. if (input_Result_Menu == status) {
  264. status = input_Result_Cancel;
  265. }
  266. if (0 == status) status = choices[menu.cursor];
  267. wait_for_input_quiet(input, &sys->input);
  268. if (NULL != state) *state = menu;
  269. return status;
  270. }
  271. int modal_popup(const char* message, nes_Renderer* rend,
  272. nes_Input_Reader* input, nes* sys) {
  273. int w = 0;
  274. int h = 0;
  275. nes_text_size(rend, message, &w, &h);
  276. int x = ((int)nes_ppu_render_w - w) / 2;
  277. int y = ((int)nes_ppu_render_h - h) / 2;
  278. if (x < 5) x = 5;
  279. if (y < 5) y = 5;
  280. nes_draw_last_frame(rend, NULL != sys->cart.mapper);
  281. nes_draw_text(rend, message, x, y, color_error);
  282. nes_draw_done(rend);
  283. return wait_for_input(input, &sys->input);
  284. }