Mirror of CollapseOS
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.

378 lines
9.4KB

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <stdbool.h>
  4. #include <unistd.h>
  5. #include <xcb/xcb.h>
  6. #define XK_MISCELLANY
  7. #include <X11/keysymdef.h>
  8. #include "emul.h"
  9. #include "sms_vdp.h"
  10. #include "sms_ports.h"
  11. #include "sms_pad.h"
  12. #include "ps2_kbd.h"
  13. #include "sdc.h"
  14. #define RAMSTART 0xc000
  15. #define VDP_CMD_PORT 0xbf
  16. #define VDP_DATA_PORT 0xbe
  17. #define PORTS_CTL_PORT 0x3f
  18. #define PORTS_IO1_PORT 0xdc
  19. #define PORTS_IO2_PORT 0xdd
  20. #define SDC_CTL 0x05
  21. #define SDC_SPI 0x04
  22. #define MAX_ROMSIZE 0x8000
  23. static xcb_connection_t *conn;
  24. static xcb_screen_t *screen;
  25. /* graphics contexts */
  26. static xcb_gcontext_t fg;
  27. /* win */
  28. static xcb_drawable_t win;
  29. // pixels to draw. We draw them in one shot.
  30. static xcb_rectangle_t rectangles[VDP_SCREENW*VDP_SCREENH];
  31. static Machine *m;
  32. static VDP vdp;
  33. static bool vdp_changed;
  34. static Ports ports;
  35. static Pad pad;
  36. static Kbd kbd;
  37. static bool use_kbd = false;
  38. static SDC sdc;
  39. static uint8_t iord_vdp_cmd()
  40. {
  41. return vdp_cmd_rd(&vdp);
  42. }
  43. static uint8_t iord_vdp_data()
  44. {
  45. return vdp_data_rd(&vdp);
  46. }
  47. static uint8_t iord_ports_io1()
  48. {
  49. return ports_A_rd(&ports);
  50. }
  51. static uint8_t iord_ports_io2()
  52. {
  53. return ports_B_rd(&ports);
  54. }
  55. static uint8_t iord_pad()
  56. {
  57. return pad_rd(&pad);
  58. }
  59. static uint8_t iord_kbd()
  60. {
  61. return kbd_rd(&kbd);
  62. }
  63. static void iowr_vdp_cmd(uint8_t val)
  64. {
  65. vdp_cmd_wr(&vdp, val);
  66. }
  67. static void iowr_vdp_data(uint8_t val)
  68. {
  69. vdp_changed = true;
  70. vdp_data_wr(&vdp, val);
  71. }
  72. static void iowr_ports_ctl(uint8_t val)
  73. {
  74. ports_ctl_wr(&ports, val);
  75. }
  76. // TODO: re-add as controller-based SPI
  77. /* static uint8_t iord_sdc_spi()
  78. {
  79. return sdc_spi_rd(&sdc);
  80. }
  81. static void iowr_sdc_spi(uint8_t val)
  82. {
  83. sdc_spi_wr(&sdc, val);
  84. }
  85. // in emulation, exchanges are always instantaneous, so we
  86. // always report as ready.
  87. static uint8_t iord_sdc_ctl()
  88. {
  89. return 0;
  90. }
  91. static void iowr_sdc_ctl(uint8_t val)
  92. {
  93. sdc_ctl_wr(&sdc, val);
  94. }*/
  95. void create_window()
  96. {
  97. uint32_t mask;
  98. uint32_t values[2];
  99. /* Create the window */
  100. win = xcb_generate_id(conn);
  101. mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
  102. values[0] = screen->white_pixel;
  103. values[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS |
  104. XCB_EVENT_MASK_KEY_RELEASE;
  105. xcb_create_window(
  106. conn,
  107. screen->root_depth,
  108. win,
  109. screen->root,
  110. 0, 0,
  111. 500, 500,
  112. 10,
  113. XCB_WINDOW_CLASS_INPUT_OUTPUT,
  114. screen->root_visual,
  115. mask, values);
  116. fg = xcb_generate_id(conn);
  117. mask = XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES;
  118. values[0] = screen->black_pixel;
  119. values[1] = 0;
  120. xcb_create_gc(conn, fg, screen->root, mask, values);
  121. /* Map the window on the screen */
  122. xcb_map_window(conn, win);
  123. }
  124. // To make things simple with X11, we only support monochrome display, which is
  125. // inverted: As soon as the color of the pixel is non-black, we show a black
  126. // pixel. If the pixel is white, we show black.
  127. void draw_pixels()
  128. {
  129. xcb_get_geometry_reply_t *geom;
  130. geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
  131. xcb_clear_area(
  132. conn, 0, win, 0, 0, geom->width, geom->height);
  133. // Figure out inner size to maximize our screen's aspect ratio
  134. int psize = geom->height / VDP_SCREENH;
  135. if (geom->width / VDP_SCREENW < psize) {
  136. // width is the constraint
  137. psize = geom->width / VDP_SCREENW;
  138. }
  139. int innerw = psize * VDP_SCREENW;
  140. int innerh = psize * VDP_SCREENH;
  141. int innerx = (geom->width - innerw) / 2;
  142. int innery = (geom->height - innerh) / 2;
  143. free(geom);
  144. int drawcnt = 0;
  145. for (int i=0; i<VDP_SCREENW; i++) {
  146. for (int j=0; j<VDP_SCREENH; j++) {
  147. if (vdp_pixel(&vdp, i, j)) {
  148. int x = innerx + (i*psize);
  149. int y = innery + (j*psize);
  150. rectangles[drawcnt].x = x;
  151. rectangles[drawcnt].y = y;
  152. rectangles[drawcnt].height = psize;
  153. rectangles[drawcnt].width = psize;
  154. drawcnt++;
  155. }
  156. }
  157. }
  158. if (drawcnt) {
  159. xcb_poly_fill_rectangle(
  160. conn, win, fg, drawcnt, rectangles);
  161. }
  162. vdp_changed = false;
  163. xcb_flush(conn);
  164. }
  165. // Returns true to exist event loop
  166. static bool _handle_keypress(xcb_generic_event_t *e)
  167. {
  168. xcb_key_press_event_t *ev = (xcb_key_press_event_t *)e;
  169. if (ev->detail == 0x09) { // ESC
  170. return true;
  171. }
  172. bool ispressed = e->response_type == XCB_KEY_PRESS;
  173. // change keycode into symbol
  174. xcb_get_keyboard_mapping_reply_t* km = xcb_get_keyboard_mapping_reply(
  175. conn, xcb_get_keyboard_mapping(conn, ev->detail, 1), NULL);
  176. if (km->length) {
  177. xcb_keysym_t* keysyms = (xcb_keysym_t*)(km + 1);
  178. if (use_kbd) {
  179. if ((keysyms[0] == XK_Shift_L) || (keysyms[0] == XK_Shift_R)) {
  180. kbd_pressshift(&kbd, ispressed);
  181. } else if (ispressed) {
  182. fprintf(stderr, "pressing %x\n", keysyms[0]);
  183. kbd_presskey(&kbd, keysyms[0]);
  184. }
  185. } else { // pad
  186. switch (keysyms[0]) {
  187. case 'w':
  188. pad_setbtn(&pad, PAD_BTN_UP, ispressed);
  189. break;
  190. case 'a':
  191. pad_setbtn(&pad, PAD_BTN_LEFT, ispressed);
  192. break;
  193. case 's':
  194. pad_setbtn(&pad, PAD_BTN_DOWN, ispressed);
  195. break;
  196. case 'd':
  197. pad_setbtn(&pad, PAD_BTN_RIGHT, ispressed);
  198. break;
  199. case 'h':
  200. pad_setbtn(&pad, PAD_BTN_A, ispressed);
  201. break;
  202. case 'j':
  203. pad_setbtn(&pad, PAD_BTN_B, ispressed);
  204. break;
  205. case 'k':
  206. pad_setbtn(&pad, PAD_BTN_C, ispressed);
  207. break;
  208. case 'l':
  209. pad_setbtn(&pad, PAD_BTN_START, ispressed);
  210. break;
  211. }
  212. }
  213. }
  214. free(km);
  215. return false;
  216. }
  217. void event_loop()
  218. {
  219. while (1) {
  220. if (!emul_step()) {
  221. fprintf(stderr, "CPU halted, quitting\n");
  222. usleep(1000 * 1000);
  223. break;
  224. }
  225. if (vdp_changed) {
  226. // To avoid overdrawing, we'll let the CPU run a bit to finish its
  227. // drawing operation.
  228. emul_steps(10000);
  229. draw_pixels();
  230. }
  231. // A low tech way of checking when the window was closed. The proper way
  232. // involving WM_DELETE is too complicated.
  233. xcb_get_geometry_reply_t *geom;
  234. geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
  235. if (geom == NULL) {
  236. return; // window has been closed.
  237. } else {
  238. free(geom);
  239. }
  240. xcb_generic_event_t *e = xcb_poll_for_event(conn);
  241. if (!e) {
  242. continue;
  243. }
  244. switch (e->response_type & ~0x80) {
  245. /* ESC to exit */
  246. case XCB_KEY_RELEASE:
  247. case XCB_KEY_PRESS:
  248. if (_handle_keypress(e)) return;
  249. break;
  250. case XCB_EXPOSE: {
  251. draw_pixels();
  252. break;
  253. }
  254. default: {
  255. break;
  256. }
  257. }
  258. free(e);
  259. }
  260. }
  261. static void usage()
  262. {
  263. fprintf(stderr, "Usage: ./sms [-k] [-c sdcard.img] /path/to/rom\n");
  264. }
  265. int main(int argc, char *argv[])
  266. {
  267. if (argc < 2) {
  268. usage();
  269. return 1;
  270. }
  271. vdp_init(&vdp);
  272. vdp_changed = false;
  273. ports_init(&ports);
  274. pad_init(&pad, &ports.THA);
  275. kbd_init(&kbd, &ports.THA);
  276. sdc_init(&sdc);
  277. int ch;
  278. while ((ch = getopt(argc, argv, "kc:")) != -1) {
  279. switch (ch) {
  280. case 'k':
  281. use_kbd = true;
  282. break;
  283. case 'c':
  284. fprintf(stderr, "Setting up SD card image with %s\n", optarg);
  285. sdc.fp = fopen(optarg, "r+");
  286. if (sdc.fp == NULL) {
  287. fprintf(stderr, "Can't open file\n");
  288. return 1;
  289. }
  290. break;
  291. }
  292. }
  293. if (optind != argc-1) {
  294. usage();
  295. return 1;
  296. }
  297. FILE *fp = fopen(argv[optind], "r");
  298. if (fp == NULL) {
  299. fprintf(stderr, "Can't open %s\n", argv[1]);
  300. return 1;
  301. }
  302. m = emul_init();
  303. m->ramstart = RAMSTART;
  304. int i = 0;
  305. int c;
  306. while ((c = fgetc(fp)) != EOF && i < MAX_ROMSIZE) {
  307. m->mem[i++] = c & 0xff;
  308. }
  309. pclose(fp);
  310. if (c != EOF) {
  311. fprintf(stderr, "ROM image too large.\n");
  312. return 1;
  313. }
  314. if (use_kbd) {
  315. ports.portA_rd = iord_kbd;
  316. } else {
  317. ports.portA_rd = iord_pad;
  318. }
  319. m->iord[VDP_CMD_PORT] = iord_vdp_cmd;
  320. m->iord[VDP_DATA_PORT] = iord_vdp_data;
  321. m->iord[PORTS_IO1_PORT] = iord_ports_io1;
  322. m->iord[PORTS_IO2_PORT] = iord_ports_io2;
  323. m->iord[PORTS_CTL_PORT] = iord_noop;
  324. m->iowr[VDP_CMD_PORT] = iowr_vdp_cmd;
  325. m->iowr[VDP_DATA_PORT] = iowr_vdp_data;
  326. m->iowr[PORTS_CTL_PORT] = iowr_ports_ctl;
  327. /* TODO: re-add
  328. m->iord[SDC_SPI] = iord_sdc_spi;
  329. m->iowr[SDC_SPI] = iowr_sdc_spi;
  330. m->iord[SDC_CTL] = iord_sdc_ctl;
  331. m->iowr[SDC_CTL] = iowr_sdc_ctl;
  332. */
  333. conn = xcb_connect(NULL, NULL);
  334. screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
  335. create_window();
  336. draw_pixels();
  337. event_loop();
  338. emul_printdebug();
  339. if (sdc.fp) {
  340. fclose(sdc.fp);
  341. }
  342. return 0;
  343. }