diff --git a/emul/hw/sms/.gitignore b/emul/hw/sms/.gitignore new file mode 100644 index 0000000..2ad1042 --- /dev/null +++ b/emul/hw/sms/.gitignore @@ -0,0 +1 @@ +/sms diff --git a/emul/hw/sms/Makefile b/emul/hw/sms/Makefile new file mode 100644 index 0000000..3e802de --- /dev/null +++ b/emul/hw/sms/Makefile @@ -0,0 +1,15 @@ +EXTOBJS = ../../emul.o ../../libz80/libz80.o +OBJS = sms.o vdp.o +TARGET = sms +CFLAGS += `pkg-config --cflags xcb` + +.PHONY: all +all: $(TARGET) + +$(TARGET): $(OBJS) $(EXTOBJS) + $(CC) `pkg-config --libs xcb` $(OBJS) $(EXTOBJS) -o $@ + +.PHONY: clean +clean: + rm -f $(TARGET) $(OBJS) + diff --git a/emul/hw/sms/sms.c b/emul/hw/sms/sms.c new file mode 100644 index 0000000..89e4865 --- /dev/null +++ b/emul/hw/sms/sms.c @@ -0,0 +1,194 @@ +#include +#include +#include + +#include + +#include "../../emul.h" +#include "vdp.h" + +#define RAMSTART 0xc000 +#define VDP_CMD_PORT 0xbf +#define VDP_DATA_PORT 0xbe +#define MAX_ROMSIZE 0x8000 + +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[VDP_SCREENW*VDP_SCREENH]; + +static Machine *m; +static VDP vdp; +static bool vdp_changed; + +static uint8_t iord_vdp_cmd() +{ + return vdp_cmd_rd(&vdp); +} + +static uint8_t iord_vdp_data() +{ + return vdp_data_rd(&vdp); +} + +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); +} + +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, + 150, 150, + 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); +} + +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_SCREENH; + if (geom->width / VDP_SCREENW < psize) { + // width is the constraint + psize = geom->width / VDP_SCREENW; + } + int innerw = psize * VDP_SCREENW; + int innerh = psize * VDP_SCREENH; + int innerx = (geom->width - innerw) / 2; + int innery = (geom->height - innerh) / 2; + int drawcnt = 0; + for (int i=0; iresponse_type & ~0x80) { + /* ESC to exit */ + case XCB_KEY_RELEASE: + case XCB_KEY_PRESS: { + xcb_key_press_event_t *ev = (xcb_key_press_event_t *)e; + if (ev->detail == 0x09) return; + break; + } + case XCB_EXPOSE: { + draw_pixels(); + break; + } + default: { + break; + } + } + free(e); + } +} + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + fprintf(stderr, "Usage: ./sms /path/to/rom\n"); + return 1; + } + FILE *fp = fopen(argv[1], "r"); + if (fp == NULL) { + fprintf(stderr, "Can't open %s\n", argv[1]); + return 1; + } + m = emul_init(); + m->ramstart = RAMSTART; + int i = 0; + int c; + while ((c = fgetc(fp)) != EOF && i < MAX_ROMSIZE) { + m->mem[i++] = c & 0xff; + } + pclose(fp); + if (c != EOF) { + fprintf(stderr, "ROM image too large.\n"); + return 1; + } + vdp_init(&vdp); + vdp_changed = false; + m->iord[VDP_CMD_PORT] = iord_vdp_cmd; + m->iord[VDP_DATA_PORT] = iord_vdp_data; + m->iowr[VDP_CMD_PORT] = iowr_vdp_cmd; + m->iowr[VDP_DATA_PORT] = iowr_vdp_data; + conn = xcb_connect(NULL, NULL); + screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; + create_window(); + draw_pixels(); + event_loop(); + emul_printdebug(); + return 0; +} diff --git a/emul/hw/sms/vdp.c b/emul/hw/sms/vdp.c new file mode 100644 index 0000000..2a7f789 --- /dev/null +++ b/emul/hw/sms/vdp.c @@ -0,0 +1,77 @@ +#include +#include "vdp.h" + +void vdp_init(VDP *vdp) +{ + memset(vdp->vram, 0, VDP_VRAM_SIZE); + memset(vdp->regs, 0, 0x10); + vdp->has_cmdlsb = false; + vdp->curaddr = 0; +} + +uint8_t vdp_cmd_rd(VDP *vdp) +{ + return 0; +} + +void vdp_cmd_wr(VDP *vdp, uint8_t val) +{ + if (!vdp->has_cmdlsb) { + vdp->cmdlsb = val; + vdp->has_cmdlsb = true; + return; + } + vdp->has_cmdlsb = false; + if ((val & 0xc0) == 0x80) { + // set register + vdp->regs[val&0xf] = vdp->cmdlsb; + } else if ((val & 0xc0) == 0xc0) { + // palette RAM + vdp->curaddr = 0x4000 + (vdp->cmdlsb&0x1f); + } else { + // VRAM + vdp->curaddr = ((val&0x3f) << 8) + vdp->cmdlsb; + } +} + +uint8_t vdp_data_rd(VDP *vdp) +{ + uint8_t res = vdp->vram[vdp->curaddr]; + if (vdp->curaddr < VDP_VRAM_SIZE) { + vdp->curaddr++; + } + return res; +} + +void vdp_data_wr(VDP *vdp, uint8_t val) +{ + vdp->vram[vdp->curaddr] = val; + if (vdp->curaddr < VDP_VRAM_SIZE) { + vdp->curaddr++; + } +} + +uint8_t vdp_pixel(VDP *vdp, uint16_t x, uint16_t y) +{ + if (x >= VDP_SCREENW) { + return 0; + } + if (y >= VDP_SCREENH) { + return 0; + } + // name table offset + uint16_t offset = 0x3800 + ((y/8) << 6) + ((x/8) << 1); + uint16_t tableval = vdp->vram[offset] + (vdp->vram[offset+1] << 8); + uint16_t tilenum = tableval & 0x1ff; + // tile offset this time. Each tile is 0x20 bytes long. + offset = tilenum * 0x20; + // Each 4 byte is a row. Find row first. + offset += ((y%8) * 4); + uint8_t bitnum = 7 - (x%8); + // Now, let's compose the result by pushing the right bit of our 4 bytes + // into our result. + return ((vdp->vram[offset] >> bitnum) & 1) + \ + (((vdp->vram[offset+1] >> bitnum) & 1) << 1) + \ + (((vdp->vram[offset+2] >> bitnum) & 1) << 2) + \ + (((vdp->vram[offset+3] >> bitnum) & 1) << 3); +} diff --git a/emul/hw/sms/vdp.h b/emul/hw/sms/vdp.h new file mode 100644 index 0000000..e36e014 --- /dev/null +++ b/emul/hw/sms/vdp.h @@ -0,0 +1,26 @@ +#include +#include + +#define VDP_VRAM_SIZE 0x4020 +#define VDP_SCREENW (32*8) +#define VDP_SCREENH (24*8) +// Offset of the name table +#define VDP_NTABLE_OFFSET 0x3800 + + +typedef struct { + // the last 0x20 is palette RAM + uint8_t vram[VDP_VRAM_SIZE]; + uint8_t regs[0x10]; + uint8_t cmdlsb; + bool has_cmdlsb; + uint16_t curaddr; +} VDP; + +void vdp_init(VDP *vdp); +uint8_t vdp_cmd_rd(VDP *vdp); +void vdp_cmd_wr(VDP *vdp, uint8_t val); +uint8_t vdp_data_rd(VDP *vdp); +void vdp_data_wr(VDP *vdp, uint8_t val); +// result is a RGB value +uint8_t vdp_pixel(VDP *vdp, uint16_t x, uint16_t y);