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.

341 wiersze
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. }