|
- #include <stdlib.h>
- #include <stdio.h>
- #include <stdbool.h>
- #include <unistd.h>
-
- #include <xcb/xcb.h>
- #define XK_MISCELLANY
- #include <X11/keysymdef.h>
-
- #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; i<vdp.tms.width; i++) {
- for (int j=0; j<vdp.tms.height; j++) {
- if (vdp_pixel(&vdp, i, j)) {
- int x = innerx + (i*psize);
- int y = innery + (j*psize);
- rectangles[drawcnt].x = x;
- rectangles[drawcnt].y = y;
- rectangles[drawcnt].height = psize;
- rectangles[drawcnt].width = psize;
- drawcnt++;
- }
- }
- }
- if (drawcnt) {
- xcb_poly_fill_rectangle(
- conn, win, fg, drawcnt, rectangles);
- }
- vdp_changed = false;
- xcb_flush(conn);
- }
-
- // Returns true to exist event loop
- static bool _handle_keypress(xcb_generic_event_t *e)
- {
- xcb_key_press_event_t *ev = (xcb_key_press_event_t *)e;
- if (ev->detail == 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;
- }
|