@@ -193,3 +193,6 @@ void emul_printdebug() | |||
fprintf(stderr, "Min SP: %04x\n", m.minsp); | |||
fprintf(stderr, "Max IX: %04x\n", m.maxix); | |||
} | |||
byte iord_noop() { return 0; } | |||
void iowr_noop(byte val) {} |
@@ -37,3 +37,6 @@ void emul_trace(ushort addr); | |||
void emul_memdump(); | |||
void emul_debugstr(char *s); | |||
void emul_printdebug(); | |||
// use when a port is a NOOP, but it's not an error to access it. | |||
byte iord_noop(); | |||
void iowr_noop(byte val); |
@@ -1,5 +1,5 @@ | |||
EXTOBJS = ../../emul.o ../../libz80/libz80.o | |||
OBJS = sms.o vdp.o port.o pad.o | |||
OBJS = sms.o vdp.o port.o pad.o kbd.o | |||
TARGET = sms | |||
CFLAGS += `pkg-config --cflags xcb` | |||
LDFLAGS += `pkg-config --libs xcb` | |||
@@ -25,4 +25,9 @@ pad are: | |||
* K --> C | |||
* L --> Start | |||
If your ROM is configured with PS/2 keyboard input, run this emulator with the | |||
`-k` flag to replace SMS pad emulation with keyboard emulation. | |||
In both cases (pad or keyboard), only port A emulation is supported. | |||
Press ESC to quit. |
@@ -0,0 +1,101 @@ | |||
#include "kbd.h" | |||
void kbd_init(Kbd *kbd, Tristate *TH) | |||
{ | |||
kbd->kc = 0; | |||
kbd->breaking = false; | |||
kbd->TH = TH; | |||
} | |||
void kbd_pressshift(Kbd *kbd, bool ispressed) | |||
{ | |||
kbd->kc = 0x12; // shift keycode | |||
kbd->breaking = !ispressed; | |||
} | |||
void kbd_presskey(Kbd *kbd, uint8_t key) | |||
{ | |||
kbd->kc = 0; | |||
switch (key) { | |||
case '\t': kbd->kc = 0xd; break; | |||
case '`': kbd->kc = 0xe; break; | |||
case 'q': kbd->kc = 0x15; break; | |||
case '1': kbd->kc = 0x16; break; | |||
case 'z': kbd->kc = 0x1a; break; | |||
case 's': kbd->kc = 0x1b; break; | |||
case 'a': kbd->kc = 0x1c; break; | |||
case 'w': kbd->kc = 0x1d; break; | |||
case '2': kbd->kc = 0x1e; break; | |||
case 'c': kbd->kc = 0x21; break; | |||
case 'x': kbd->kc = 0x22; break; | |||
case 'd': kbd->kc = 0x23; break; | |||
case 'e': kbd->kc = 0x24; break; | |||
case '4': kbd->kc = 0x25; break; | |||
case '3': kbd->kc = 0x26; break; | |||
case ' ': kbd->kc = 0x29; break; | |||
case 'v': kbd->kc = 0x2a; break; | |||
case 'f': kbd->kc = 0x2b; break; | |||
case 't': kbd->kc = 0x2c; break; | |||
case 'r': kbd->kc = 0x2d; break; | |||
case '5': kbd->kc = 0x2e; break; | |||
case 'n': kbd->kc = 0x31; break; | |||
case 'b': kbd->kc = 0x32; break; | |||
case 'h': kbd->kc = 0x33; break; | |||
case 'g': kbd->kc = 0x34; break; | |||
case 'y': kbd->kc = 0x35; break; | |||
case '6': kbd->kc = 0x36; break; | |||
case 'm': kbd->kc = 0x3a; break; | |||
case 'j': kbd->kc = 0x3b; break; | |||
case 'u': kbd->kc = 0x3c; break; | |||
case '7': kbd->kc = 0x3d; break; | |||
case '8': kbd->kc = 0x3e; break; | |||
case ',': kbd->kc = 0x41; break; | |||
case 'k': kbd->kc = 0x42; break; | |||
case 'i': kbd->kc = 0x43; break; | |||
case 'o': kbd->kc = 0x44; break; | |||
case '0': kbd->kc = 0x45; break; | |||
case '9': kbd->kc = 0x46; break; | |||
case '.': kbd->kc = 0x49; break; | |||
case '/': kbd->kc = 0x4a; break; | |||
case 'l': kbd->kc = 0x4b; break; | |||
case ';': kbd->kc = 0x4c; break; | |||
case 'p': kbd->kc = 0x4d; break; | |||
case '-': kbd->kc = 0x4e; break; | |||
case '\'': kbd->kc = 0x52; break; | |||
case '[': kbd->kc = 0x54; break; | |||
case '=': kbd->kc = 0x55; break; | |||
case '\r': kbd->kc = 0x5a; break; | |||
case ']': kbd->kc = 0x5b; break; | |||
case '\\': kbd->kc = 0x5d; break; | |||
case '\b': kbd->kc = 0x66; break; | |||
} | |||
} | |||
uint8_t kbd_rd(Kbd *kbd) | |||
{ | |||
// There are 3 modes for reading the kbd: | |||
// 1. TH = highz: we're polling the the existence of a KC | |||
// 2. TH = low: return low nibble | |||
// 3. TH = high: return high nibble and reset KC | |||
uint8_t res = 0; | |||
uint8_t tosend = kbd->kc; | |||
if (kbd->breaking) { | |||
tosend = 0xf0; | |||
} | |||
if (*kbd->TH == TRI_HIGHZ) { // polling | |||
if (!kbd->kc) { | |||
res = 0x10; // indicate KC absence; | |||
} | |||
} else if (*kbd->TH == TRI_LOW) { // TH selected | |||
res = tosend & 0xf; | |||
} else { | |||
res = tosend >> 4; | |||
if (kbd->breaking) { | |||
kbd->breaking = false; | |||
} else { | |||
kbd->kc = 0; | |||
} | |||
} | |||
return res; | |||
} | |||
@@ -0,0 +1,16 @@ | |||
#include <stdint.h> | |||
#include <stdbool.h> | |||
#include "port.h" | |||
#define KBD_BUFSZ 0x10 | |||
typedef struct { | |||
uint8_t kc; // last keycode to be pressed. 0 means none. | |||
bool breaking; // whether we should send 0xf0 before kc | |||
Tristate *TH; | |||
} Kbd; | |||
void kbd_init(Kbd *kbd, Tristate *TH); | |||
void kbd_pressshift(Kbd *kbd, bool ispressed); | |||
void kbd_presskey(Kbd *kbd, uint8_t keycode); | |||
uint8_t kbd_rd(Kbd *kbd); |
@@ -11,6 +11,7 @@ | |||
#include "vdp.h" | |||
#include "port.h" | |||
#include "pad.h" | |||
#include "kbd.h" | |||
#define RAMSTART 0xc000 | |||
#define VDP_CMD_PORT 0xbf | |||
@@ -36,6 +37,8 @@ static VDP vdp; | |||
static bool vdp_changed; | |||
static Ports ports; | |||
static Pad pad; | |||
static Kbd kbd; | |||
static bool use_kbd = false; | |||
static uint8_t iord_vdp_cmd() | |||
{ | |||
@@ -62,6 +65,11 @@ 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); | |||
@@ -159,38 +167,49 @@ void draw_pixels() | |||
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); | |||
switch (keysyms[0]) { | |||
case XK_Escape: free(km); return true; | |||
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; | |||
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); | |||
@@ -242,13 +261,30 @@ void event_loop() | |||
} | |||
} | |||
static void usage() | |||
{ | |||
fprintf(stderr, "Usage: ./sms [-k] /path/to/rom\n"); | |||
} | |||
int main(int argc, char *argv[]) | |||
{ | |||
if (argc != 2) { | |||
fprintf(stderr, "Usage: ./sms /path/to/rom\n"); | |||
if (argc < 2) { | |||
usage(); | |||
return 1; | |||
} | |||
int ch; | |||
while ((ch = getopt(argc, argv, "k")) != -1) { | |||
switch (ch) { | |||
case 'k': | |||
use_kbd = true; | |||
break; | |||
} | |||
} | |||
if (optind != argc-1) { | |||
usage(); | |||
return 1; | |||
} | |||
FILE *fp = fopen(argv[1], "r"); | |||
FILE *fp = fopen(argv[optind], "r"); | |||
if (fp == NULL) { | |||
fprintf(stderr, "Can't open %s\n", argv[1]); | |||
return 1; | |||
@@ -268,12 +304,18 @@ int main(int argc, char *argv[]) | |||
vdp_init(&vdp); | |||
vdp_changed = false; | |||
ports_init(&ports); | |||
ports.portA_rd = iord_pad; | |||
pad_init(&pad, &ports.THA); | |||
kbd_init(&kbd, &ports.THA); | |||
if (use_kbd) { | |||
ports.portA_rd = iord_kbd; | |||
} else { | |||
ports.portA_rd = iord_pad; | |||
} | |||
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; | |||
@@ -105,7 +105,7 @@ to TH (and also the A/B on the '157). Q is hooked to PB0 and TL. | |||
We start with the base recipe and add a few things: | |||
1. at the top: `SYSVARS 0x72 + CONSTANT PS2_MEM` | |||
2. After VDP load: `641 LOAD : (ps2kc) (ps2kcB) ;` (that binds us to port B) | |||
2. After VDP load: `621 LOAD : (ps2kc) (ps2kcB) ;` (that binds us to port B) | |||
3. Right after: `411 414 LOADR` (that gives us `(key)`) | |||
4. After `VDP$`: `PS2$`. | |||