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.
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

346 行
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. }