changeset 2521:8cf7cadc17ee

Initial SC-3000 support
author Michael Pavone <pavone@retrodev.com>
date Fri, 11 Oct 2024 00:46:53 -0700
parents 0e9d7ef03983
children 1de9eb7cbf38
files Makefile blastem.c config.c default.cfg i8255.c i8255.h rom.db sms.c sms.h system.c systems.cfg
diffstat 11 files changed, 581 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Mon Oct 07 21:38:54 2024 -0700
+++ b/Makefile	Fri Oct 11 00:46:53 2024 -0700
@@ -264,8 +264,8 @@
 ifdef NOZ80
 CFLAGS+=-DNO_Z80
 else
-MAINOBJS+= sms.o $(Z80OBJS)
-LIBOBJS+= sms.o $(Z80OBJS)
+MAINOBJS+= sms.o i8255.o $(Z80OBJS)
+LIBOBJS+= sms.o i8255.o $(Z80OBJS)
 endif
 
 ifeq ($(OS),Windows)
--- a/blastem.c	Mon Oct 07 21:38:54 2024 -0700
+++ b/blastem.c	Fri Oct 11 00:46:53 2024 -0700
@@ -130,7 +130,7 @@
 
 uint32_t load_media_zip(const char *filename, system_media *dst)
 {
-	static const char *valid_exts[] = {"bin", "md", "gen", "sms", "gg", "rom", "smd", "sg"};
+	static const char *valid_exts[] = {"bin", "md", "gen", "sms", "gg", "rom", "smd", "sg", "sc", "sf7"};
 	const uint32_t num_exts = sizeof(valid_exts)/sizeof(*valid_exts);
 	zip_file *z = zip_open(filename);
 	if (!z) {
--- a/config.c	Mon Oct 07 21:38:54 2024 -0700
+++ b/config.c	Fri Oct 11 00:46:53 2024 -0700
@@ -316,7 +316,7 @@
 	*pads = tern_insert_node(*pads, key, val.ptrval);
 }
 
-#define CONFIG_VERSION 9
+#define CONFIG_VERSION 10
 static tern_node *migrate_config(tern_node *config, int from_version)
 {
 	tern_node *def_config = parse_bundled_config("default.cfg");
@@ -479,6 +479,43 @@
 		}
 		free(exts[0]);//All extensions in this list share an allocation, first one is a pointer to the buffer
 		free(exts);
+		break;
+	}
+	case 9: {
+		//Add pre-SMS 8-bit image formats to ui.extensions
+		uint32_t num_exts;
+		char **ext_list = get_extension_list(config, &num_exts);
+		char *old = num_exts ? ext_list[0] : NULL;
+		uint32_t new_size = num_exts + 3;
+		uint8_t need_sc = 1, need_sg = 1, need_sf7 = 1;
+		for (uint32_t i = 0; i < num_exts; i++)
+		{
+			if (!strcmp(ext_list[i], "sc")) {
+				need_sc = 0;
+				new_size--;
+			} else if (!strcmp(ext_list[i], "sg")) {
+				need_sg = 0;
+				new_size--;
+			} else if (!strcmp(ext_list[i], "sf7")) {
+				need_sf7 = 0;
+				new_size--;
+			}
+		}
+		if (new_size != num_exts) {
+			ext_list = realloc(ext_list, sizeof(char*) * new_size);
+			if (need_sc) {
+				ext_list[num_exts++] = "sc";
+			}
+			if (need_sg) {
+				ext_list[num_exts++] = "sg";
+			}
+			if (need_sf7) {
+				ext_list[num_exts++] = "sf7";
+			}
+		}
+		char *combined = alloc_join(new_size, (char const **)ext_list, ' ');
+		config = tern_insert_path(config, "ui\0extensions\0", (tern_val){.ptrval = combined}, TVAL_PTR);
+		break;
 	}
 	}
 	char buffer[16];
--- a/default.cfg	Mon Oct 07 21:38:54 2024 -0700
+++ b/default.cfg	Fri Oct 11 00:46:53 2024 -0700
@@ -401,7 +401,7 @@
 	#accepts special variables $HOME, $EXEDIR, $USERDATA, $ROMNAME
 	save_path $USERDATA/blastem/$ROMNAME
 	#space delimited list of file extensions to filter against in menu
-	extensions bin gen md smd sms gg zip gz cue iso vgm vgz flac wav col
+	extensions bin gen md smd sms gg sg sc sf7 zip gz cue iso vgm vgz flac wav col
 	#specifies the preferred save-state format, set to gst for Genecyst compatible states
 	state_format native
 	#set to on to use the native file picker on your OS instead of the builtin one
@@ -437,4 +437,4 @@
 }
 
 #Don't manually edit `version`, it's used for automatic config migration
-version 9
+version 10
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/i8255.c	Fri Oct 11 00:46:53 2024 -0700
@@ -0,0 +1,290 @@
+#include "i8255.h"
+
+#include <string.h>
+
+#define BIT_OBFA      0x80
+#define BIT_ACKA      0x40
+#define BIT_IBFA      0x20
+#define BIT_STBA      0x10
+#define BIT_INTRA     0x08
+#define BIT_STB_ACKB  0x04
+#define BIT_IBF_OBFB  0x02
+#define BIT_INTRB     0x01
+
+#define BIT_INTE1     BIT_ACKA
+#define BIT_INTE2     BIT_STBA
+#define BIT_INTEB     BIT_STB_ACKB
+
+void i8255_init(i8255 *ppi, i8255_out_update out, i8255_in_sample in)
+{
+	memset(ppi->latches, 0, sizeof(ppi->latches));
+	ppi->control = 0x1B; //all ports start as input
+	ppi->portc_write_mask = 0xFF;
+	ppi->portc_out_mask = 0;
+	ppi->out_handler = out;
+	ppi->in_handler = in;
+}
+
+static uint8_t porta_out_enabled(i8255 *ppi)
+{
+	return (ppi->control & 0x40) || !(ppi->control & 0x10);
+}
+
+void i8255_write(uint32_t address, i8255 *ppi, uint8_t value, uint32_t cycle)
+{
+	switch(address)
+	{
+	case 0:
+		ppi->latches[0] = value;
+		if (porta_out_enabled(ppi)) {
+			if (ppi->control & 0x60) {
+				//Mode 1 or 2
+				ppi->latches[2] &= ~BIT_OBFA;
+				if ((ppi->control & 0x60) == 0x20 || !(ppi->latches[2] & BIT_IBFA)) {
+					ppi->latches[2] &= ~BIT_INTRA;
+				}
+				if (ppi->out_handler) {
+					ppi->out_handler(ppi, cycle, 2, ppi->latches[2] & ppi->portc_out_mask);
+				}
+			}
+			if (ppi->out_handler && !(ppi->control & 0x40)) {
+				ppi->out_handler(ppi, cycle, address, value);
+			}
+		}
+		break;
+	case 1:
+		if (!(ppi->control & 0x02)) {
+			ppi->latches[1] = value;
+			if (ppi->control & 0x04) {
+				//Mode 1
+				ppi->latches[2] &= ~(BIT_IBF_OBFB|BIT_INTRB);
+				if (ppi->out_handler) {
+					ppi->out_handler(ppi, cycle, 2, ppi->latches[2] & ppi->portc_out_mask);
+				}
+			}
+			if (ppi->out_handler) {
+				ppi->out_handler(ppi, cycle, address, value);
+			}
+		}
+		break;
+	case 2:
+		ppi->latches[2] &= ~ppi->portc_write_mask;
+		ppi->latches[2] |= value & ppi->portc_write_mask;
+		if (ppi->out_handler && ppi->portc_out_mask) {
+			ppi->out_handler(ppi, cycle, address, ppi->latches[2] & ppi->portc_out_mask);
+		}
+		break;
+	case 3:
+		if (value & 0x80) {
+			uint8_t changed = ppi->control ^ value;
+			//datasheet says "output" state is cleared on mode changes
+			if (changed & 0x60) {
+				//group A mode changed
+				ppi->latches[0] = 0;
+				ppi->latches[2] &= 0x0F;
+			}
+			if (changed & 4) {
+				//group B mode changed
+				ppi->latches[1] = 0;
+				if (value & 0x60) {
+					//PC4 is INTRa
+					ppi->latches[2] &= 0xF8;
+				} else {
+					ppi->latches[2] &= 0xF0;
+				}
+			}
+			ppi->control = value;
+			ppi->portc_write_mask = ppi->portc_out_mask = 0;
+			if (value & 0x40) {
+				//Port A Mode 2
+				ppi->portc_out_mask |= BIT_OBFA | BIT_IBFA | BIT_INTRA;
+				ppi->portc_write_mask |= BIT_INTE1 | BIT_INTE2;
+			} else if (value & 0x20) {
+				//Port A Mode 1
+					ppi->portc_out_mask |= BIT_INTRA;
+				if (value & 0x10) {
+					//Input
+					ppi->portc_out_mask |= BIT_IBFA;
+					ppi->portc_write_mask |= BIT_INTE2 | 0xC0;
+					if (!(value & 0x08)) {
+						//Port C upper Output
+						ppi->portc_out_mask |= 0xC0;
+					}
+				} else {
+					//Output
+					ppi->portc_out_mask |= BIT_OBFA;
+					ppi->portc_out_mask |= BIT_INTE1 | 0x30;
+					if (!(value & 0x08)) {
+						//Port C upper Output
+						ppi->portc_out_mask |= 0x30;
+					}
+				}
+			} else {
+				ppi->portc_write_mask |= 0xF0;
+				if (!(value & 0x08)) {
+					//Port C upper Output
+					ppi->portc_out_mask |= 0xF0;
+				}
+			}
+			if (value & 0x04) {
+				//Port B Mode 1
+				ppi->portc_out_mask |= BIT_IBF_OBFB | BIT_INTRB;
+				ppi->portc_write_mask |= BIT_INTEB;
+				if (!(ppi->portc_out_mask & BIT_INTRA) && !(value & 1)) {
+					//Port C lower Output
+					ppi->portc_out_mask |= 0x08;
+					ppi->portc_write_mask |= 0x08;
+				}
+			} else {
+				if (!(value & 1)) {
+					//Port C lower Output
+					ppi->portc_out_mask |= 0x07;
+					ppi->portc_write_mask |= 0x07;
+					if (!(ppi->portc_out_mask & BIT_INTRA)) {
+						ppi->portc_out_mask |= 0x08;
+						ppi->portc_write_mask |= 0x08;
+					}
+				}
+			}
+		} else {
+			uint8_t bit = 1 << ((value >> 1) & 7);
+			if (ppi->portc_write_mask & bit) {
+				if (value & 1) {
+					ppi->latches[2] |= bit;
+				} else {
+					ppi->latches[2] &= bit;
+				}
+				if (ppi->out_handler && ppi->portc_out_mask) {
+					ppi->out_handler(ppi, cycle, 2, ppi->latches[2] & ppi->portc_out_mask);
+				}
+			}
+		}
+		
+	}
+}
+
+uint8_t i8255_read(uint32_t address, i8255 *ppi, uint32_t cycle)
+{
+	switch(address)
+	{
+	case 0:
+		if (ppi->control & 0x60) {
+			//Mode 1 or 2
+			if (ppi->control & 0x50) {
+				//Mode 2 or Mode 1 input
+				ppi->latches[2] &= ~BIT_IBFA;
+				if (!(ppi->control & 0x40) || (ppi->latches[2] & BIT_OBFA)) {
+					ppi->latches[2] &= ~BIT_INTRA;
+				}
+				if (ppi->out_handler) {
+					ppi->out_handler(ppi, cycle, 2, ppi->latches[2] & ppi->portc_out_mask);
+				}
+			}
+			return ppi->latches[3];
+		}
+		if (ppi->control & 0x10) {
+			if (ppi->in_handler) {
+				return ppi->in_handler(ppi, cycle, address);
+			}
+			return 0xFF;
+		}
+		return ppi->latches[0];
+	case 1:
+		if (ppi->control & 0x40) {
+			//Mode 1
+			if (ppi->control & 0x2) {
+				//input
+				ppi->latches[2] &= ~(BIT_IBF_OBFB|BIT_INTRB);
+				if (ppi->out_handler) {
+					ppi->out_handler(ppi, cycle, 2, ppi->latches[2] & ppi->portc_out_mask);
+				}
+			}
+			return ppi->latches[1];
+		}
+		if (ppi->control & 0x2) {
+			//input
+			if (ppi->in_handler) {
+				return ppi->in_handler(ppi, cycle, address);
+			}
+			return 0xFF;
+		}
+		return ppi->latches[1];
+	case 2:
+		return ppi->latches[2];
+	case 3:
+	default:
+		return 0xFF;//described as illegal in datasheet
+	}
+}
+
+void i8255_input_strobe_a(i8255 *ppi, uint8_t value, uint32_t cycle)
+{
+	if ((ppi->control & 0x70) == 0x30 || (ppi->control & 0x40)) {
+		//Mode 2 or Mode 1 input
+		ppi->latches[3] = value;
+		ppi->latches[2] |= BIT_IBFA;
+		if (ppi->latches[2] & BIT_INTE2) {
+			ppi->latches[2] |= BIT_INTRA;
+		}
+		if (ppi->out_handler) {
+			ppi->out_handler(ppi, cycle, 2, ppi->latches[2] & ppi->portc_out_mask);
+		}
+	}
+}
+
+void i8255_input_strobe_b(i8255 *ppi, uint8_t value, uint32_t cycle)
+{
+	if ((ppi->control & 6) == 6) {
+		//Mode 1 input
+		ppi->latches[1] = value;
+		ppi->latches[2] |= BIT_IBF_OBFB;
+		if (ppi->latches[2] & BIT_INTEB) {
+			ppi->latches[2] |= BIT_INTRB;
+		}
+		if (ppi->out_handler) {
+			ppi->out_handler(ppi, cycle, 2, ppi->latches[2] & ppi->portc_out_mask);
+		}
+	}
+}
+
+uint8_t i8255_output_ack_a(i8255 *ppi, uint32_t cycle)
+{
+	if ((ppi->control & 0x70) == 0x20 || (ppi->control & 0x40)) {
+		//Mode 2 or Mode 1 output
+		ppi->latches[2] |= BIT_OBFA;
+		if (ppi->latches[2] & BIT_INTE1) {
+			ppi->latches[2] |= BIT_INTRA;
+		}
+		if (ppi->out_handler) {
+			ppi->out_handler(ppi, cycle, 2, ppi->latches[2] & ppi->portc_out_mask);
+		}
+		return ppi->latches[0];
+	}
+	if (ppi->control & 0x10) {
+		//input mode
+		return 0xFF;
+	}
+	//Mode 0 output
+	return ppi->latches[0];
+}
+
+uint8_t i8255_output_ack_b(i8255 *ppi, uint32_t cycle)
+{
+	if ((ppi->control & 0x06) == 0x04) {
+		//Mode 1 output
+		ppi->latches[2] |= BIT_IBF_OBFB;
+		if (ppi->latches[2] & BIT_INTEB) {
+			ppi->latches[2] |= BIT_INTRB;
+		}
+		if (ppi->out_handler) {
+			ppi->out_handler(ppi, cycle, 2, ppi->latches[2] & ppi->portc_out_mask);
+		}
+		return ppi->latches[1];
+	}
+	if (ppi->control & 2) {
+		//input mode
+		return 0xFF;
+	}
+	//Mode 0 output
+	return ppi->latches[1];
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/i8255.h	Fri Oct 11 00:46:53 2024 -0700
@@ -0,0 +1,28 @@
+#ifndef I8255_H_
+#define I8255_H_
+
+#include <stdint.h>
+
+typedef struct i8255 i8255;
+typedef void (*i8255_out_update)(i8255 *ppi, uint32_t cycle, uint32_t port, uint8_t data);
+typedef uint8_t (*i8255_in_sample)(i8255 *ppi, uint32_t cycle, uint32_t port);
+
+struct i8255 {
+	uint8_t          latches[4];
+	uint8_t          control;
+	uint8_t          portc_write_mask;
+	uint8_t          portc_out_mask;
+	i8255_out_update out_handler;
+	i8255_in_sample  in_handler;
+	void             *system;
+};
+
+void i8255_init(i8255 *ppi, i8255_out_update out, i8255_in_sample in);
+void i8255_write(uint32_t address, i8255 *ppi, uint8_t value, uint32_t cycle);
+uint8_t i8255_read(uint32_t address, i8255 *ppi, uint32_t cycle);
+void i8255_input_strobe_a(i8255 *ppi, uint8_t value, uint32_t cycle);
+void i8255_input_strobe_b(i8255 *ppi, uint8_t value, uint32_t cycle);
+uint8_t i8255_output_ack_a(i8255 *ppi, uint32_t cycle);
+uint8_t i8255_output_ack_b(i8255 *ppi, uint32_t cycle);
+
+#endif //I8255_H_
--- a/rom.db	Mon Oct 07 21:38:54 2024 -0700
+++ b/rom.db	Fri Oct 11 00:46:53 2024 -0700
@@ -1689,3 +1689,17 @@
 	#German version of this game has 8G in the region field
 	regions E
 }
+a43aef367857a681decea52377c2e7a992c2ac68 {
+	name Level III BASIC
+	map {
+		0 {
+			device ROM
+			last 7FFF
+		}
+		8000 {
+			device RAM
+			size 8000
+			last FFFF
+		}
+	}
+}
--- a/sms.c	Mon Oct 07 21:38:54 2024 -0700
+++ b/sms.c	Fri Oct 11 00:46:53 2024 -0700
@@ -126,6 +126,41 @@
 	return 0xFF;
 }
 
+static void i8255_output_updated(i8255 *ppi, uint32_t cycle, uint32_t port, uint8_t data)
+{
+	if (port == 2) {
+		sms_context *sms = ppi->system;
+		sms->kb_mux = data & 0x7;
+	}
+}
+
+static uint8_t i8255_input_poll(i8255 *ppi, uint32_t cycle, uint32_t port)
+{
+	if (port > 1) {
+		return 0xFF;
+	}
+	sms_context *sms = ppi->system;
+	if (sms->kb_mux == 7) {
+		if (port) {
+			//TODO: cassette-in
+			//TODO: printer port BUSY/FAULT
+			uint8_t port_b = io_data_read(sms->io.ports+1, cycle);
+			return (port_b >> 2 & 0xF) | 0x10;
+		} else {
+			uint8_t port_a = io_data_read(sms->io.ports, cycle);
+			uint8_t port_b = io_data_read(sms->io.ports+1, cycle);
+			return (port_a & 0x3F) | (port_b << 6);
+		}
+	}
+	//TODO: keyboard matrix ghosting
+	if (port) {
+		//TODO: cassette-in
+		//TODO: printer port BUSY/FAULT
+		return (sms->keystate[sms->kb_mux] >> 8) | 0x10;
+	}
+	return sms->keystate[sms->kb_mux];
+}
+
 static void update_mem_map(uint32_t location, sms_context *sms, uint8_t value)
 {
 	z80_context *z80 = sms->z80;
@@ -249,6 +284,54 @@
 	sms->psg->pan = value;
 	return vcontext;
 }
+
+static void *ppi_write(uint32_t location, void *vcontext, uint8_t value)
+{
+	z80_context *z80 = vcontext;
+	sms_context *sms = z80->system;
+	i8255_write(location, sms->i8255, value, z80->Z80_CYCLE);
+	return vcontext;
+}
+
+static uint8_t ppi_read(uint32_t location, void *vcontext)
+{
+	z80_context *z80 = vcontext;
+	sms_context *sms = z80->system;
+	return i8255_read(location, sms->i8255, z80->Z80_CYCLE);
+}
+
+static void *all_write(uint32_t location, void *vcontext, uint8_t value)
+{
+	vdp_write(location, vcontext, value);
+	sms_psg_write(location, vcontext, value);
+	return ppi_write(location, vcontext, value);
+}
+
+static uint8_t ppi_vdp_read(uint32_t location, void *vcontext)
+{
+	//TODO: "corrupt" PPI value by VDP value
+	vdp_read(location, vcontext);
+	return ppi_read(location, vcontext);
+}
+
+static void *vdp_psg_write(uint32_t location, void *vcontext, uint8_t value)
+{
+	vdp_write(location, vcontext, value);
+	return sms_psg_write(location, vcontext, value);
+}
+
+static void *ppi_psg_write(uint32_t location, void *vcontext, uint8_t value)
+{
+	vdp_write(location, vcontext, value);
+	return ppi_write(location, vcontext, value);
+}
+
+static void *ppi_vdp_write(uint32_t location, void *vcontext, uint8_t value)
+{
+	vdp_write(location, vcontext, value);
+	return ppi_write(location, vcontext, value);
+}
+
 static memmap_chunk io_map[] = {
 	{0x00, 0x40, 0xFF, .write_8 = memory_io_write},
 	{0x40, 0x80, 0xFF, .read_8 = hv_read, .write_8 = sms_psg_write},
@@ -265,6 +348,16 @@
 	{0xC0, 0x100,0xFF, .read_8 = io_read}
 };
 
+static memmap_chunk io_sc[] = {
+	{0x00, 0x20, 0x03, .read_8 = ppi_vdp_read, .write_8 = all_write},
+	{0x20, 0x40, 0xFF, .read_8 = vdp_read, .write_8 = vdp_psg_write},
+	{0x40, 0x60, 0x03, .read_8 = ppi_read, .write_8 = ppi_psg_write},
+	{0x60, 0x80, 0xFF, .write_8 = sms_psg_write},
+	{0x80, 0xA0, 0x03, .read_8 = ppi_vdp_read, .write_8 = ppi_vdp_write},
+	{0xA0, 0xC0, 0xFF, .read_8 = vdp_read, .write_8 = vdp_write},
+	{0xD0, 0x100, 0x03, .read_8 = ppi_read, .write_8 = ppi_write}
+};
+
 static void set_speed_percent(system_header * system, uint32_t percent)
 {
 	sms_context *context = (sms_context *)system;
@@ -589,6 +682,7 @@
 	z80_options_free(sms->z80->Z80_OPTS);
 	free(sms->z80);
 	psg_free(sms->psg);
+	free(sms->i8255);
 	free(sms);
 }
 
@@ -670,16 +764,92 @@
 	io_mouse_motion_relative(&sms->io, mouse_num, x, y);
 }
 
+uint16_t scancode_map[0x90] = {
+	[0x1C] = 0x0004,//A
+	[0x32] = 0x4008,//B
+	[0x21] = 0x2008,//C
+	[0x23] = 0x2004,//D
+	[0x24] = 0x2002,//E
+	[0x2B] = 0x3004,//F
+	[0x34] = 0x4004,//G
+	[0x33] = 0x5004,//H
+	[0x43] = 0x0080,//I
+	[0x3B] = 0x6004,//J
+	[0x42] = 0x0040,//K
+	[0x4B] = 0x1040,//L
+	[0x3A] = 0x6008,//M
+	[0x31] = 0x5008,//N
+	[0x44] = 0x1080,//O
+	[0x4D] = 0x2080,//P
+	[0x15] = 0x0002,//Q
+	[0x2D] = 0x3002,//R
+	[0x1B] = 0x1004,//S
+	[0x2C] = 0x4002,//T
+	[0x3C] = 0x6002,//U
+	[0x2A] = 0x3008,//V
+	[0x1D] = 0x1002,//W
+	[0x22] = 0x1008,//X
+	[0x35] = 0x5002,//Y
+	[0x1A] = 0x0008,//Z
+	[0x16] = 0x0001,//1
+	[0x1E] = 0x1001,//2
+	[0x26] = 0x2001,//3
+	[0x25] = 0x3001,//4
+	[0x2E] = 0x4001,//5
+	[0x36] = 0x5001,//6
+	[0x3D] = 0x6001,//7
+	[0x3E] = 0x0100,//8
+	[0x46] = 0x1100,//9
+	[0x45] = 0x2100,//0
+	[0x5A] = 0x5040,//return
+	[0x29] = 0x1010,//space
+	[0x0D] = 0x5800,//tab mapped to FUNC
+	[0x66] = 0x3010,//backspace mapped to INS/DEL
+	[0x4E] = 0x3100,// -
+	[0x55] = 0x4100,// = mapped to ^ based on position
+	[0x54] = 0x4080,// [
+	[0x5B] = 0x4040,// ]
+	[0x5D] = 0x5100,// \ mapped to Yen based on position/correspondence on PC keyboards
+	[0x4C] = 0x2040,// ;
+	[0x52] = 0x3040,// ' mapped to : based on position
+	[0x0E] = 0x3020,// ` mapped to PI because of lack of good options
+	[0x41] = 0x0020,// ,
+	[0x49] = 0x1020,// .
+	[0x4A] = 0x2020,// /
+	[0x14] = 0x6400,//lctrl mapped to ctrl
+	//rctrl is default keybind for toggle keyboard capture
+	//[0x18] = 0x6400,//rctrl mapped to ctrl
+	[0x12] = 0x6800,//lshift mapped to shift
+	[0x59] = 0x6800,//lshift mapped to shift
+	[0x11] = 0x6200,//lalt mapped to GRAPH
+	[0x17] = 0x6200,//ralt mapped to GRAPH
+	[0x81] = 0x0010,//insert mapped to kana/dieresis key
+	[0x86] = 0x5020,//left arrow
+	[0x87] = 0x2010,//home mapped to HOME/CLR
+	[0x88] = 0x6100,//end mapped to BREAK
+	[0x89] = 0x6040,//up arrow
+	[0x8A] = 0x4020,//down arrow
+	[0x8D] = 0x6020,//right arrow
+};
+
 static void keyboard_down(system_header *system, uint8_t scancode)
 {
 	sms_context *sms = (sms_context *)system;
 	io_keyboard_down(&sms->io, scancode);
+	if (sms->keystate && scancode < 0x90 && scancode_map[scancode]) {
+		uint16_t row = scancode_map[scancode] >> 12;
+		sms->keystate[row] &= ~(scancode_map[scancode] & 0xFFF);
+	}
 }
 
 static void keyboard_up(system_header *system, uint8_t scancode)
 {
 	sms_context *sms = (sms_context *)system;
 	io_keyboard_up(&sms->io, scancode);
+	if (sms->keystate && scancode < 0x90 && scancode_map[scancode]) {
+		uint16_t row = scancode_map[scancode] >> 12;
+		sms->keystate[row] |= scancode_map[scancode] & 0xFFF;
+	}
 }
 
 static void set_gain_config(sms_context *sms)
@@ -728,15 +898,18 @@
 	z80_options *zopts = malloc(sizeof(z80_options));
 	tern_node *model_def;
 	uint8_t is_gamegear = !strcasecmp(media->extension, "gg");
+	uint8_t is_sc3000 = !strcasecmp(media->extension, "sc");
 	if (is_gamegear) {
 		model_def = tern_find_node(get_systems_config(), "gg");
 	} else if (!strcasecmp(media->extension, "sg")) {
 		model_def = tern_find_node(get_systems_config(), "sg1000");
+	} else if (is_sc3000) {
+		model_def = tern_find_node(get_systems_config(), "sc3000");
 	} else {
 		model_def = get_model(config, SYSTEM_SMS);
 	}
 	char *vdp_str = tern_find_ptr(model_def, "vdp");
-	uint8_t vdp_type = is_gamegear ? VDP_GENESIS : VDP_GAMEGEAR;
+	uint8_t vdp_type = is_gamegear ? VDP_GAMEGEAR : is_sc3000 ? VDP_TMS9918A : VDP_SMS2;
 	if (vdp_str) {
 		if (!strcmp(vdp_str, "sms1")) {
 			vdp_type = VDP_SMS;
@@ -759,9 +932,27 @@
 			chunk->buffer = sms->ram + ((chunk->start - 0xC000) & 0x1FFF);
 		}
 	}
+	char *io_type = tern_find_ptr(model_def, "io");
+	if (io_type) {
+		if (!strcmp(io_type, "gamegear")) {
+			is_gamegear = 1;
+			is_sc3000 = 0;
+		} else if (!strcmp(io_type, "i8255")) {
+			is_gamegear = 0;
+			is_sc3000 = 1;
+		}
+	}
 	if (is_gamegear) {
 		init_z80_opts(zopts, sms->header.info.map, sms->header.info.map_chunks, io_gg, 6, 15, 0xFF);
 		sms->start_button_region = 0xC0;
+	} else if (is_sc3000) {
+		sms->keystate = calloc(sizeof(uint16_t), 7);
+		memset(sms->keystate, 0xFF, sizeof(uint16_t) * 7);
+		sms->i8255 = calloc(1, sizeof(i8255));
+		i8255_init(sms->i8255, i8255_output_updated, i8255_input_poll);
+		sms->i8255->system = sms;
+		sms->kb_mux = 7;
+		init_z80_opts(zopts, sms->header.info.map, sms->header.info.map_chunks, io_sc, 7, 15, 0xFF);
 	} else {
 		init_z80_opts(zopts, sms->header.info.map, sms->header.info.map_chunks, io_map, 4, 15, 0xFF);
 	}
@@ -803,7 +994,7 @@
 		}
 	}
 	setup_io_devices(io_config_root, &sms->header.info, &sms->io);
-	sms->header.has_keyboard = io_has_keyboard(&sms->io);
+	sms->header.has_keyboard = io_has_keyboard(&sms->io) || sms->keystate;
 
 	sms->header.set_speed_percent = set_speed_percent;
 	sms->header.start_context = start_sms;
--- a/sms.h	Mon Oct 07 21:38:54 2024 -0700
+++ b/sms.h	Fri Oct 11 00:46:53 2024 -0700
@@ -10,6 +10,7 @@
 #include "z80_to_x86.h"
 #endif
 #include "io.h"
+#include "i8255.h"
 
 #define SMS_RAM_SIZE (8*1024)
 #define SMS_CART_RAM_SIZE (32*1024)
@@ -20,6 +21,8 @@
 	vdp_context   *vdp;
 	psg_context   *psg;
 	sega_io       io;
+	i8255         *i8255;
+	uint16_t      *keystate;
 	uint8_t       *rom;
 	uint32_t      rom_size;
 	uint32_t      master_clock;
@@ -30,6 +33,7 @@
 	uint8_t       ram[SMS_RAM_SIZE];
 	uint8_t       bank_regs[4];
 	uint8_t       cart_ram[SMS_CART_RAM_SIZE];
+	uint8_t       kb_mux;
 } sms_context;
 
 sms_context *alloc_configure_sms(system_media *media, uint32_t opts, uint8_t force_region);
--- a/system.c	Mon Oct 07 21:38:54 2024 -0700
+++ b/system.c	Fri Oct 11 00:46:53 2024 -0700
@@ -86,7 +86,8 @@
 		if (!strcmp("md", media->extension) || !strcmp("gen", media->extension)) {
 			return SYSTEM_GENESIS;
 		}
-		if (!strcmp("sms", media->extension) || !strcmp("sg", media->extension) || !strcmp("gg", media->extension)) {
+		if (!strcmp("sms", media->extension) || !strcmp("sg", media->extension) || !strcmp("gg", media->extension)
+			|| !strcmp("sc", media->extension) || !strcmp("sf7", media->extension)) {
 			return SYSTEM_SMS;
 		}
 		if (!strcmp("j64", media->extension)) {
--- a/systems.cfg	Mon Oct 07 21:38:54 2024 -0700
+++ b/systems.cfg	Fri Oct 11 00:46:53 2024 -0700
@@ -97,6 +97,7 @@
 gg {
 	name Game Gear
 	vdp gamegear
+	io gamegear
 	show no
 }
 sg1000 {
@@ -104,3 +105,9 @@
 	vdp tms9918a
 	show no
 }
+sc3000 {
+	name SC-3000
+	vdp tms9918a
+	io i8255
+	show no
+}