#include #include #include #include #include #define XK_MISCELLANY #include #define MAX_ROMSIZE 0x8000 #include "emul.h" #include "sms_vdp.h" #include "sms_ports.h" #include "sms_pad.h" #include "sms_spi.h" #include "ps2_kbd.h" #include "sdc.h" #define RAMSTART 0xc000 #define VDP_CMD_PORT 0xbf #define VDP_DATA_PORT 0xbe #define PORTS_CTL_PORT 0x3f #define PORTS_IO1_PORT 0xdc #define PORTS_IO2_PORT 0xdd #define SDC_CTL 0x05 #define SDC_SPI 0x04 static xcb_connection_t *conn; static xcb_screen_t *screen; /* graphics contexts */ static xcb_gcontext_t fg; /* win */ static xcb_drawable_t win; // pixels to draw. We draw them in one shot. static xcb_rectangle_t rectangles[(32*8)*(24*8)]; static Machine *m; static VDP vdp; static bool vdp_changed; static Ports ports; static Pad pad; static Kbd kbd; static bool use_kbd = false; static SDC sdc; static SPI spi; static uint8_t iord_vdp_cmd() { return tms_cmd_rd(&vdp.tms); } static uint8_t iord_vdp_data() { return vdp_data_rd(&vdp); } static uint8_t iord_ports_io1() { return ports_A_rd(&ports); } static uint8_t iord_ports_io2() { return ports_B_rd(&ports); } static uint8_t iord_pad() { return pad_rd(&pad); } static uint8_t iord_kbd() { return kbd_rd(&kbd); } static void iowr_vdp_cmd(uint8_t val) { vdp_cmd_wr(&vdp, val); } static void iowr_vdp_data(uint8_t val) { vdp_changed = true; vdp_data_wr(&vdp, val); } static void iowr_ports_ctl(uint8_t val) { ports_ctl_wr(&ports, val); } static byte iord_spi() { return spi_rd(&spi); } static byte spix_sdc(byte val) { return sdc_spix(&sdc, val); } void create_window() { uint32_t mask; uint32_t values[2]; /* Create the window */ win = xcb_generate_id(conn); mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; values[0] = screen->white_pixel; values[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE; xcb_create_window( conn, screen->root_depth, win, screen->root, 0, 0, 500, 500, 10, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, mask, values); fg = xcb_generate_id(conn); mask = XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES; values[0] = screen->black_pixel; values[1] = 0; xcb_create_gc(conn, fg, screen->root, mask, values); /* Map the window on the screen */ xcb_map_window(conn, win); } // To make things simple with X11, we only support monochrome display, which is // inverted: As soon as the color of the pixel is non-black, we show a black // pixel. If the pixel is white, we show black. void draw_pixels() { xcb_get_geometry_reply_t *geom; geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL); xcb_clear_area( conn, 0, win, 0, 0, geom->width, geom->height); // Figure out inner size to maximize our screen's aspect ratio int psize = geom->height / vdp.tms.height; if (geom->width / vdp.tms.width < psize) { // width is the constraint psize = geom->width / vdp.tms.width; } int innerw = psize * vdp.tms.width; int innerh = psize * vdp.tms.height; int innerx = (geom->width - innerw) / 2; int innery = (geom->height - innerh) / 2; free(geom); int drawcnt = 0; for (int i=0; idetail == 0x09) { // ESC return true; } bool ispressed = e->response_type == XCB_KEY_PRESS; // change keycode into symbol xcb_get_keyboard_mapping_reply_t* km = xcb_get_keyboard_mapping_reply( conn, xcb_get_keyboard_mapping(conn, ev->detail, 1), NULL); if (km->length) { xcb_keysym_t* keysyms = (xcb_keysym_t*)(km + 1); if (use_kbd) { if ((keysyms[0] == XK_Shift_L) || (keysyms[0] == XK_Shift_R)) { kbd_pressshift(&kbd, ispressed); } else if (ispressed) { fprintf(stderr, "pressing %x\n", keysyms[0]); kbd_presskey(&kbd, keysyms[0]); } } else { // pad switch (keysyms[0]) { case 'w': pad_setbtn(&pad, PAD_BTN_UP, ispressed); break; case 'a': pad_setbtn(&pad, PAD_BTN_LEFT, ispressed); break; case 's': pad_setbtn(&pad, PAD_BTN_DOWN, ispressed); break; case 'd': pad_setbtn(&pad, PAD_BTN_RIGHT, ispressed); break; case 'h': pad_setbtn(&pad, PAD_BTN_A, ispressed); break; case 'j': pad_setbtn(&pad, PAD_BTN_B, ispressed); break; case 'k': pad_setbtn(&pad, PAD_BTN_C, ispressed); break; case 'l': pad_setbtn(&pad, PAD_BTN_START, ispressed); break; } } } free(km); return false; } void event_loop() { while (1) { for (int i=0; i<100; i++) { if (!emul_step()) { fprintf(stderr, "CPU halted, quitting\n"); usleep(1000 * 1000); break; } spi_pulse(&spi); } if (vdp_changed) { // To avoid overdrawing, we'll let the CPU run a bit to finish its // drawing operation. for (int i=0; i<10000; i++) { if (!emul_step()) { fprintf(stderr, "CPU halted, quitting\n"); usleep(1000 * 1000); break; } spi_pulse(&spi); } draw_pixels(); } // A low tech way of checking when the window was closed. The proper way // involving WM_DELETE is too complicated. xcb_get_geometry_reply_t *geom; geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL); if (geom == NULL) { return; // window has been closed. } else { free(geom); } xcb_generic_event_t *e = xcb_poll_for_event(conn); if (!e) { continue; } switch (e->response_type & ~0x80) { /* ESC to exit */ case XCB_KEY_RELEASE: case XCB_KEY_PRESS: if (_handle_keypress(e)) return; break; case XCB_EXPOSE: { draw_pixels(); break; } default: { break; } } free(e); } } static void usage() { fprintf(stderr, "Usage: ./sms [-k] [-c sdcard.img] /path/to/rom\n"); } static byte spi_dbg(byte val) { fprintf(stderr, "SPI XCH: %x\n", val); return val+1; } int main(int argc, char *argv[]) { if (argc < 2) { usage(); return 1; } vdp_init(&vdp); vdp_changed = false; ports_init(&ports); pad_init(&pad, &ports.THA); kbd_init(&kbd, &ports.THA); sdc_init(&sdc); spi_init(&spi, &ports.THB, &ports.TRB, spix_sdc); int ch; while ((ch = getopt(argc, argv, "kc:")) != -1) { switch (ch) { case 'k': use_kbd = true; break; case 'c': fprintf(stderr, "Setting up SD card image with %s\n", optarg); sdc.fp = fopen(optarg, "r+"); if (sdc.fp == NULL) { fprintf(stderr, "Can't open file\n"); return 1; } break; } } if (optind != argc-1) { usage(); return 1; } FILE *fp = fopen(argv[optind], "r"); if (fp == NULL) { fprintf(stderr, "Can't open %s\n", argv[1]); return 1; } m = emul_init(argv[optind], 0); if (m == NULL) return 1; m->ramstart = RAMSTART; if (use_kbd) { ports.portA_rd = iord_kbd; } else { ports.portA_rd = iord_pad; } ports.portB_rd = iord_spi; m->iord[VDP_CMD_PORT] = iord_vdp_cmd; m->iord[VDP_DATA_PORT] = iord_vdp_data; m->iord[PORTS_IO1_PORT] = iord_ports_io1; m->iord[PORTS_IO2_PORT] = iord_ports_io2; m->iord[PORTS_CTL_PORT] = iord_noop; m->iowr[VDP_CMD_PORT] = iowr_vdp_cmd; m->iowr[VDP_DATA_PORT] = iowr_vdp_data; m->iowr[PORTS_CTL_PORT] = iowr_ports_ctl; conn = xcb_connect(NULL, NULL); screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; create_window(); draw_pixels(); event_loop(); emul_printdebug(); if (sdc.fp) { fclose(sdc.fp); } return 0; }