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.

288 lines
7.3KB

  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 "vdp.h"
  10. #include "port.h"
  11. #include "pad.h"
  12. #define RAMSTART 0xc000
  13. #define VDP_CMD_PORT 0xbf
  14. #define VDP_DATA_PORT 0xbe
  15. #define PORTS_CTL_PORT 0x3f
  16. #define PORTS_IO1_PORT 0xdc
  17. #define PORTS_IO2_PORT 0xdd
  18. #define MAX_ROMSIZE 0x8000
  19. static xcb_connection_t *conn;
  20. static xcb_screen_t *screen;
  21. /* graphics contexts */
  22. static xcb_gcontext_t fg;
  23. /* win */
  24. static xcb_drawable_t win;
  25. // pixels to draw. We draw them in one shot.
  26. static xcb_rectangle_t rectangles[VDP_SCREENW*VDP_SCREENH];
  27. static Machine *m;
  28. static VDP vdp;
  29. static bool vdp_changed;
  30. static Ports ports;
  31. static Pad pad;
  32. static uint8_t iord_vdp_cmd()
  33. {
  34. return vdp_cmd_rd(&vdp);
  35. }
  36. static uint8_t iord_vdp_data()
  37. {
  38. return vdp_data_rd(&vdp);
  39. }
  40. static uint8_t iord_ports_io1()
  41. {
  42. return ports_A_rd(&ports);
  43. }
  44. static uint8_t iord_ports_io2()
  45. {
  46. return ports_B_rd(&ports);
  47. }
  48. static uint8_t iord_pad()
  49. {
  50. return pad_rd(&pad);
  51. }
  52. static void iowr_vdp_cmd(uint8_t val)
  53. {
  54. vdp_cmd_wr(&vdp, val);
  55. }
  56. static void iowr_vdp_data(uint8_t val)
  57. {
  58. vdp_changed = true;
  59. vdp_data_wr(&vdp, val);
  60. }
  61. static void iowr_ports_ctl(uint8_t val)
  62. {
  63. ports_ctl_wr(&ports, val);
  64. }
  65. void create_window()
  66. {
  67. uint32_t mask;
  68. uint32_t values[2];
  69. /* Create the window */
  70. win = xcb_generate_id(conn);
  71. mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
  72. values[0] = screen->white_pixel;
  73. values[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS |
  74. XCB_EVENT_MASK_KEY_RELEASE;
  75. xcb_create_window(
  76. conn,
  77. screen->root_depth,
  78. win,
  79. screen->root,
  80. 0, 0,
  81. 500, 500,
  82. 10,
  83. XCB_WINDOW_CLASS_INPUT_OUTPUT,
  84. screen->root_visual,
  85. mask, values);
  86. fg = xcb_generate_id(conn);
  87. mask = XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES;
  88. values[0] = screen->black_pixel;
  89. values[1] = 0;
  90. xcb_create_gc(conn, fg, screen->root, mask, values);
  91. /* Map the window on the screen */
  92. xcb_map_window(conn, win);
  93. }
  94. // To make things simple with X11, we only support monochrome display, which is
  95. // inverted: As soon as the color of the pixel is non-black, we show a black
  96. // pixel. If the pixel is white, we show black.
  97. void draw_pixels()
  98. {
  99. xcb_get_geometry_reply_t *geom;
  100. geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
  101. xcb_clear_area(
  102. conn, 0, win, 0, 0, geom->width, geom->height);
  103. // Figure out inner size to maximize our screen's aspect ratio
  104. int psize = geom->height / VDP_SCREENH;
  105. if (geom->width / VDP_SCREENW < psize) {
  106. // width is the constraint
  107. psize = geom->width / VDP_SCREENW;
  108. }
  109. int innerw = psize * VDP_SCREENW;
  110. int innerh = psize * VDP_SCREENH;
  111. int innerx = (geom->width - innerw) / 2;
  112. int innery = (geom->height - innerh) / 2;
  113. free(geom);
  114. int drawcnt = 0;
  115. for (int i=0; i<VDP_SCREENW; i++) {
  116. for (int j=0; j<VDP_SCREENH; j++) {
  117. if (vdp_pixel(&vdp, i, j)) {
  118. int x = innerx + (i*psize);
  119. int y = innery + (j*psize);
  120. rectangles[drawcnt].x = x;
  121. rectangles[drawcnt].y = y;
  122. rectangles[drawcnt].height = psize;
  123. rectangles[drawcnt].width = psize;
  124. drawcnt++;
  125. }
  126. }
  127. }
  128. if (drawcnt) {
  129. xcb_poly_fill_rectangle(
  130. conn, win, fg, drawcnt, rectangles);
  131. }
  132. vdp_changed = false;
  133. xcb_flush(conn);
  134. }
  135. // Returns true to exist event loop
  136. static bool _handle_keypress(xcb_generic_event_t *e)
  137. {
  138. xcb_key_press_event_t *ev = (xcb_key_press_event_t *)e;
  139. bool ispressed = e->response_type == XCB_KEY_PRESS;
  140. // change keycode into symbol
  141. xcb_get_keyboard_mapping_reply_t* km = xcb_get_keyboard_mapping_reply(
  142. conn, xcb_get_keyboard_mapping(conn, ev->detail, 1), NULL);
  143. if (km->length) {
  144. xcb_keysym_t* keysyms = (xcb_keysym_t*)(km + 1);
  145. switch (keysyms[0]) {
  146. case XK_Escape: free(km); return true;
  147. case 'w':
  148. pad_setbtn(&pad, PAD_BTN_UP, ispressed);
  149. break;
  150. case 'a':
  151. pad_setbtn(&pad, PAD_BTN_LEFT, ispressed);
  152. break;
  153. case 's':
  154. pad_setbtn(&pad, PAD_BTN_DOWN, ispressed);
  155. break;
  156. case 'd':
  157. pad_setbtn(&pad, PAD_BTN_RIGHT, ispressed);
  158. break;
  159. case 'h':
  160. pad_setbtn(&pad, PAD_BTN_A, ispressed);
  161. break;
  162. case 'j':
  163. pad_setbtn(&pad, PAD_BTN_B, ispressed);
  164. break;
  165. case 'k':
  166. pad_setbtn(&pad, PAD_BTN_C, ispressed);
  167. break;
  168. case 'l':
  169. pad_setbtn(&pad, PAD_BTN_START, ispressed);
  170. break;
  171. }
  172. }
  173. free(km);
  174. return false;
  175. }
  176. void event_loop()
  177. {
  178. while (1) {
  179. if (!emul_step()) {
  180. fprintf(stderr, "CPU halted, quitting\n");
  181. usleep(1000 * 1000);
  182. break;
  183. }
  184. if (vdp_changed) {
  185. // To avoid overdrawing, we'll let the CPU run a bit to finish its
  186. // drawing operation.
  187. emul_steps(100);
  188. draw_pixels();
  189. }
  190. // A low tech way of checking when the window was closed. The proper way
  191. // involving WM_DELETE is too complicated.
  192. xcb_get_geometry_reply_t *geom;
  193. geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
  194. if (geom == NULL) {
  195. return; // window has been closed.
  196. } else {
  197. free(geom);
  198. }
  199. xcb_generic_event_t *e = xcb_poll_for_event(conn);
  200. if (!e) {
  201. continue;
  202. }
  203. switch (e->response_type & ~0x80) {
  204. /* ESC to exit */
  205. case XCB_KEY_RELEASE:
  206. case XCB_KEY_PRESS:
  207. if (_handle_keypress(e)) return;
  208. break;
  209. case XCB_EXPOSE: {
  210. draw_pixels();
  211. break;
  212. }
  213. default: {
  214. break;
  215. }
  216. }
  217. free(e);
  218. }
  219. }
  220. int main(int argc, char *argv[])
  221. {
  222. if (argc != 2) {
  223. fprintf(stderr, "Usage: ./sms /path/to/rom\n");
  224. return 1;
  225. }
  226. FILE *fp = fopen(argv[1], "r");
  227. if (fp == NULL) {
  228. fprintf(stderr, "Can't open %s\n", argv[1]);
  229. return 1;
  230. }
  231. m = emul_init();
  232. m->ramstart = RAMSTART;
  233. int i = 0;
  234. int c;
  235. while ((c = fgetc(fp)) != EOF && i < MAX_ROMSIZE) {
  236. m->mem[i++] = c & 0xff;
  237. }
  238. pclose(fp);
  239. if (c != EOF) {
  240. fprintf(stderr, "ROM image too large.\n");
  241. return 1;
  242. }
  243. vdp_init(&vdp);
  244. vdp_changed = false;
  245. ports_init(&ports);
  246. ports.portA_rd = iord_pad;
  247. pad_init(&pad, &ports.THA);
  248. m->iord[VDP_CMD_PORT] = iord_vdp_cmd;
  249. m->iord[VDP_DATA_PORT] = iord_vdp_data;
  250. m->iord[PORTS_IO1_PORT] = iord_ports_io1;
  251. m->iord[PORTS_IO2_PORT] = iord_ports_io2;
  252. m->iowr[VDP_CMD_PORT] = iowr_vdp_cmd;
  253. m->iowr[VDP_DATA_PORT] = iowr_vdp_data;
  254. m->iowr[PORTS_CTL_PORT] = iowr_ports_ctl;
  255. conn = xcb_connect(NULL, NULL);
  256. screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
  257. create_window();
  258. draw_pixels();
  259. event_loop();
  260. emul_printdebug();
  261. return 0;
  262. }