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.

341 lines
9.0KB

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