changeset 1692:5dacaef602a7 segacd

Merge from default
author Michael Pavone <pavone@retrodev.com>
date Sat, 05 Jan 2019 00:58:08 -0800
parents 95b3a1a8b26c (diff) b1ad6339de4f (current diff)
children 3414a4423de1
files Makefile blastem.c genesis.c genesis.h romdb.c romdb.h system.c system.h
diffstat 10 files changed, 447 insertions(+), 81 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Fri Jan 04 19:13:47 2019 -0800
+++ b/Makefile	Sat Jan 05 00:58:08 2019 -0800
@@ -163,7 +163,7 @@
 
 MAINOBJS=blastem.o system.o genesis.o debug.o gdb_remote.o vdp.o $(RENDEROBJS) io.o romdb.o hash.o menu.o xband.o \
 	realtec.o i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \
-	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o zip.o bindings.o jcart.o
+	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o zip.o bindings.o jcart.o segacd.o
 	
 ifdef NONUKLEAR
 CFLAGS+= -DDISABLE_NUKLEAR
--- a/blastem.c	Fri Jan 04 19:13:47 2019 -0800
+++ b/blastem.c	Sat Jan 05 00:58:08 2019 -0800
@@ -100,7 +100,7 @@
 	return readsize;
 }
 
-uint32_t load_rom_zip(const char *filename, void **dst)
+uint32_t load_media_zip(const char *filename, system_media *dst)
 {
 	static const char *valid_exts[] = {"bin", "md", "gen", "sms", "rom"};
 	const uint32_t num_exts = sizeof(valid_exts)/sizeof(*valid_exts);
@@ -119,9 +119,12 @@
 		{
 			if (!strcasecmp(ext, valid_exts[j])) {
 				size_t out_size = nearest_pow2(z->entries[i].size);
-				*dst = zip_read(z, i, &out_size);
-				if (*dst) {
-					free(ext);
+				dst->buffer = zip_read(z, i, &out_size);
+				if (dst->buffer) {
+					dst->extension = ext;
+					dst->dir = path_dirname(filename);
+					dst->name = basename_no_extension(filename);
+					dst->size = out_size;
 					zip_close(z);
 					return out_size;
 				}
@@ -133,13 +136,13 @@
 	return 0;
 }
 
-uint32_t load_rom(const char * filename, void **dst, system_type *stype)
+uint32_t load_media(const char * filename, system_media *dst, system_type *stype)
 {
 	uint8_t header[10];
 	char *ext = path_extension(filename);
 	if (ext && !strcasecmp(ext, "zip")) {
 		free(ext);
-		return load_rom_zip(filename, dst);
+		return load_media_zip(filename, dst);
 	}
 	free(ext);
 	ROMFILE f = romopen(filename, "rb");
@@ -150,6 +153,7 @@
 		fatal_error("Error reading from %s\n", filename);
 	}
 	
+	uint32_t ret = 0;
 	if (header[1] == SMD_MAGIC1 && header[8] == SMD_MAGIC2 && header[9] == SMD_MAGIC3) {
 		int i;
 		for (i = 3; i < 8; i++) {
@@ -164,38 +168,44 @@
 			if (stype) {
 				*stype = SYSTEM_GENESIS;
 			}
-			return load_smd_rom(f, dst);
+			ret = load_smd_rom(f, &dst->buffer);
 		}
 	}
 	
-	size_t filesize = 512 * 1024;
-	size_t readsize = sizeof(header);
+	if (!ret) {
+		size_t filesize = 512 * 1024;
+		size_t readsize = sizeof(header);
 		
-	char *buf = malloc(filesize);
-	memcpy(buf, header, readsize);
+		char *buf = malloc(filesize);
+		memcpy(buf, header, readsize);
 	
-	size_t read;
-	do {
-		read = romread(buf + readsize, 1, filesize - readsize, f);
-		if (read > 0) {
-			readsize += read;
-			if (readsize == filesize) {
-				int one_more = romgetc(f);
-				if (one_more >= 0) {
-					filesize *= 2;
-					buf = realloc(buf, filesize);
-					buf[readsize++] = one_more;
-				} else {
-					read = 0;
+		size_t read;
+		do {
+			read = romread(buf + readsize, 1, filesize - readsize, f);
+			if (read > 0) {
+				readsize += read;
+				if (readsize == filesize) {
+					int one_more = romgetc(f);
+					if (one_more >= 0) {
+						filesize *= 2;
+						buf = realloc(buf, filesize);
+						buf[readsize++] = one_more;
+					} else {
+						read = 0;
+					}
 				}
 			}
-		}
-	} while (read > 0);
-	
-	*dst = buf;
+		} while (read > 0);
+		dst->buffer = buf;
+		ret = (uint32_t)readsize;
+	}
+	dst->dir = path_dirname(filename);
+	dst->name = basename_no_extension(filename);
+	dst->extension = path_extension(filename);
+	dst->size = ret;
 	
 	romclose(f);
-	return readsize;
+	return ret;
 }
 
 
@@ -340,10 +350,7 @@
 	free(lock_on.dir);
 	free(lock_on.name);
 	free(lock_on.extension);
-	lock_on.dir = path_dirname(lock_on_path);
-	lock_on.name = basename_no_extension(lock_on_path);
-	lock_on.extension = path_extension(lock_on_path);
-	lock_on.size = load_rom(lock_on_path, &lock_on.buffer, NULL);
+	load_media(lock_on_path, &lock_on, NULL);
 }
 
 static uint32_t opts = 0;
@@ -362,16 +369,14 @@
 		//start a new arena and save old one in suspended system context
 		current_system->arena = start_new_arena();
 	}
-	system_type stype = SYSTEM_UNKNOWN;
-	if (!(cart.size = load_rom(path, &cart.buffer, &stype))) {
-		fatal_error("Failed to open %s for reading\n", path);
-	}
 	free(cart.dir);
 	free(cart.name);
 	free(cart.extension);
-	cart.dir = path_dirname(path);
-	cart.name = basename_no_extension(path);
-	cart.extension = path_extension(path);
+	system_type stype = SYSTEM_UNKNOWN;
+	if (!(cart.size = load_media(path, &cart, &stype))) {
+		fatal_error("Failed to open %s for reading\n", path);
+	}
+	
 	if (force_stype != SYSTEM_UNKNOWN) {
 		stype = force_stype;
 	}
@@ -491,12 +496,9 @@
 				if (i >= argc) {
 					fatal_error("-o must be followed by a lock on cartridge filename\n");
 				}
-				lock_on.size = load_rom(argv[i], &lock_on.buffer, NULL);
-				if (!lock_on.size) {
+				if (!load_media(argv[i], &lock_on, NULL)) {
 					fatal_error("Failed to load lock on cartridge %s\n", argv[i]);
 				}
-				lock_on.name = basename_no_extension(argv[i]);
-				lock_on.extension = path_extension(argv[i]);
 				cart.chain = &lock_on;
 				break;
 			}
@@ -525,12 +527,9 @@
 				fatal_error("Unrecognized switch %s\n", argv[i]);
 			}
 		} else if (!loaded) {
-			if (!(cart.size = load_rom(argv[i], &cart.buffer, stype == SYSTEM_UNKNOWN ? &stype : NULL))) {
+			if (!load_media(argv[i], &cart, stype == SYSTEM_UNKNOWN ? &stype : NULL)) {
 				fatal_error("Failed to open %s for reading\n", argv[i]);
 			}
-			cart.dir = path_dirname(argv[i]);
-			cart.name = basename_no_extension(argv[i]);
-			cart.extension = path_extension(argv[i]);
 			romfname = argv[i];
 			loaded = 1;
 		} else if (width < 0) {
@@ -580,7 +579,7 @@
 			romfname = "menu.bin";
 		}
 		if (is_absolute_path(romfname)) {
-			if (!(cart.size = load_rom(romfname, &cart.buffer, &stype))) {
+			if (!(cart.size = load_media(romfname, &cart, &stype))) {
 				fatal_error("Failed to open UI ROM %s for reading", romfname);
 			}
 		} else {
@@ -593,12 +592,12 @@
 				cart.buffer = realloc(cart.buffer, rom_size);
 				cart.size = rom_size;
 			}
+			cart.dir = path_dirname(romfname);
+			cart.name = basename_no_extension(romfname);
+			cart.extension = path_extension(romfname);
 		}
 		//force system detection, value on command line is only for games not the menu
 		stype = detect_system_type(&cart);
-		cart.dir = path_dirname(romfname);
-		cart.name = basename_no_extension(romfname);
-		cart.extension = path_extension(romfname);
 		loaded = 1;
 	}
 	char *state_format = tern_find_path(config, "ui\0state_format\0", TVAL_PTR).ptrval;
--- a/genesis.c	Fri Jan 04 19:13:47 2019 -0800
+++ b/genesis.c	Sat Jan 05 00:58:08 2019 -0800
@@ -4,6 +4,7 @@
  BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text.
 */
 #include "genesis.h"
+#include "segacd.h"
 #include "blastem.h"
 #include "nor.h"
 #include <stdlib.h>
@@ -1104,7 +1105,8 @@
 	} else {
 		if (gen->header.enter_debugger) {
 			gen->header.enter_debugger = 0;
-			uint32_t address = gen->cart[2] << 16 | gen->cart[3];
+			uint32_t address = read_word(4, (void **)gen->m68k->mem_pointers, &gen->m68k->options->gen, gen->m68k) << 16
+				| read_word(6, (void **)gen->m68k->mem_pointers, &gen->m68k->options->gen, gen->m68k);
 			insert_breakpoint(gen->m68k, address, gen->header.debugger_type == DEBUGGER_NATIVE ? debugger : gdb_debug_enter);
 		}
 		m68k_reset(gen->m68k);
@@ -1258,7 +1260,7 @@
 	setup_io_devices(config, &system->info, &gen->io);
 }
 
-genesis_context *alloc_init_genesis(rom_info *rom, void *main_rom, void *lock_on, uint32_t system_opts, uint8_t force_region)
+static genesis_context *shared_init(uint32_t system_opts, rom_info *rom, uint8_t force_region)
 {
 	static memmap_chunk z80_map[] = {
 		{ 0x0000, 0x4000,  0x1FFF, 0, 0, MMAP_READ | MMAP_WRITE | MMAP_CODE, NULL, NULL, NULL, NULL,              NULL },
@@ -1267,6 +1269,16 @@
 		{ 0x6000, 0x6100,  0xFFFF, 0, 0, 0,                                  NULL, NULL, NULL, NULL,              z80_write_bank_reg},
 		{ 0x7F00, 0x8000,  0x00FF, 0, 0, 0,                                  NULL, NULL, NULL, z80_vdp_port_read, z80_vdp_port_write}
 	};
+	
+	char *m68k_divider = tern_find_path(config, "clocks\0m68k_divider\0", TVAL_PTR).ptrval;
+	if (!m68k_divider) {
+		m68k_divider = "7";
+	}
+	MCLKS_PER_68K = atoi(m68k_divider);
+	if (!MCLKS_PER_68K) {
+		MCLKS_PER_68K = 7;
+	}
+	
 	genesis_context *gen = calloc(1, sizeof(genesis_context));
 	gen->header.set_speed_percent = set_speed_percent;
 	gen->header.start_context = start_genesis;
@@ -1291,7 +1303,7 @@
 	gen->header.type = SYSTEM_GENESIS;
 	gen->header.info = *rom;
 	set_region(gen, rom, force_region);
-
+	
 	gen->vdp = init_vdp_context(gen->version_reg & 0x40);
 	gen->vdp->system = &gen->header;
 	gen->frame_end = vdp_cycles_to_frame_end(gen->vdp);
@@ -1321,10 +1333,8 @@
 
 	gen->z80->system = gen;
 	gen->z80->mem_pointers[0] = gen->zram;
-	gen->z80->mem_pointers[1] = gen->z80->mem_pointers[2] = (uint8_t *)main_rom;
-
-	gen->cart = main_rom;
-	gen->lock_on = lock_on;
+	gen->z80->mem_pointers[1] = gen->z80->mem_pointers[2] = NULL;
+	
 	gen->work_ram = calloc(2, RAM_WORDS);
 	if (!strcmp("random", tern_find_path_default(config, "system\0ram_init\0", (tern_val){.ptrval = "zero"}, TVAL_PTR).ptrval))
 	{
@@ -1354,9 +1364,20 @@
 			gen->vdp->vsram[i] = rand();
 		}
 	}
+	
+	return gen;
+}
+
+genesis_context *alloc_init_genesis(rom_info *rom, void *main_rom, void *lock_on, uint32_t system_opts, uint8_t force_region)
+{
+	genesis_context *gen = shared_init(system_opts, rom, force_region);
+	gen->z80->mem_pointers[1] = gen->z80->mem_pointers[2] = (uint8_t *)main_rom;
+
+	gen->cart = main_rom;
+	gen->lock_on = lock_on;
+	
 	setup_io_devices(config, rom, &gen->io);
 	gen->header.has_keyboard = io_has_keyboard(&gen->io);
-
 	gen->mapper_type = rom->mapper_type;
 	gen->save_type = rom->save_type;
 	if (gen->save_type != SAVE_NONE) {
@@ -1411,23 +1432,22 @@
 	return gen;
 }
 
+static memmap_chunk base_map[] = {
+	{0xE00000, 0x1000000, 0xFFFF,   0, 0, MMAP_READ | MMAP_WRITE | MMAP_CODE, NULL,
+			   NULL,          NULL,         NULL,            NULL},
+	{0xC00000, 0xE00000,  0x1FFFFF, 0, 0, 0,                                  NULL,
+			   (read_16_fun)vdp_port_read,  (write_16_fun)vdp_port_write,
+			   (read_8_fun)vdp_port_read_b, (write_8_fun)vdp_port_write_b},
+	{0xA00000, 0xA12000,  0x1FFFF,  0, 0, 0,                                  NULL,
+			   (read_16_fun)io_read_w,      (write_16_fun)io_write_w,
+			   (read_8_fun)io_read,         (write_8_fun)io_write}
+};
+const size_t base_chunks = sizeof(base_map)/sizeof(*base_map); 
+
 genesis_context *alloc_config_genesis(void *rom, uint32_t rom_size, void *lock_on, uint32_t lock_on_size, uint32_t ym_opts, uint8_t force_region)
 {
-	static memmap_chunk base_map[] = {
-		{0xE00000, 0x1000000, 0xFFFF,   0, 0, MMAP_READ | MMAP_WRITE | MMAP_CODE, NULL,
-		           NULL,          NULL,         NULL,            NULL},
-		{0xC00000, 0xE00000,  0x1FFFFF, 0, 0, 0,                                  NULL,
-		           (read_16_fun)vdp_port_read,  (write_16_fun)vdp_port_write,
-		           (read_8_fun)vdp_port_read_b, (write_8_fun)vdp_port_write_b},
-		{0xA00000, 0xA12000,  0x1FFFF,  0, 0, 0,                                  NULL,
-		           (read_16_fun)io_read_w,      (write_16_fun)io_write_w,
-		           (read_8_fun)io_read,         (write_8_fun)io_write}
-	};
-	static tern_node *rom_db;
-	if (!rom_db) {
-		rom_db = load_rom_db();
-	}
-	rom_info info = configure_rom(rom_db, rom, rom_size, lock_on, lock_on_size, base_map, sizeof(base_map)/sizeof(base_map[0]));
+	tern_node *rom_db = get_rom_db();
+	rom_info info = configure_rom(rom_db, rom, rom_size, lock_on, lock_on_size, base_map, base_chunks);
 	rom = info.rom;
 	rom_size = info.rom_size;
 #ifndef BLASTEM_BIG_ENDIAN
@@ -1446,3 +1466,44 @@
 	}
 	return alloc_init_genesis(&info, rom, lock_on, ym_opts, force_region);
 }
+
+genesis_context *alloc_config_genesis_cdboot(system_media *media, uint32_t system_opts, uint8_t force_region)
+{
+	tern_node *rom_db = get_rom_db();
+	rom_info info = configure_rom(rom_db, media->buffer, media->size, NULL, 0, base_map, base_chunks);
+	
+	segacd_context *cd = alloc_configure_segacd(media, system_opts, force_region, &info);
+	genesis_context *gen = shared_init(system_opts, &info, force_region);
+	gen->cart = gen->lock_on = NULL;
+	gen->save_storage = NULL;
+	gen->save_type = SAVE_NONE;
+	gen->version_reg &= ~NO_DISK;
+	
+	gen->expansion = cd;
+	setup_io_devices(config, &info, &gen->io);
+	
+	uint32_t cd_chunks;
+	memmap_chunk *cd_map = segacd_main_cpu_map(gen->expansion, &cd_chunks);
+	memmap_chunk *map = malloc(sizeof(memmap_chunk) * (cd_chunks + base_chunks));
+	memcpy(map, cd_map, sizeof(memmap_chunk) * cd_chunks);
+	memcpy(map + cd_chunks, base_map, sizeof(memmap_chunk) * base_chunks);
+	map[cd_chunks].buffer = gen->work_ram;
+	uint32_t num_chunks = cd_chunks + base_chunks;
+	
+	m68k_options *opts = malloc(sizeof(m68k_options));
+	init_m68k_opts(opts, map, num_chunks, MCLKS_PER_68K);
+	//TODO: make this configurable
+	opts->gen.flags |= M68K_OPT_BROKEN_READ_MODIFY;
+	gen->m68k = init_68k_context(opts, NULL);
+	gen->m68k->system = gen;
+	opts->address_log = (system_opts & OPT_ADDRESS_LOG) ? fopen("address.log", "w") : NULL;
+	
+	//This must happen after the 68K context has been allocated
+	for (int i = 0; i < num_chunks; i++)
+	{
+		if (map[i].flags & MMAP_PTR_IDX) {
+			gen->m68k->mem_pointers[map[i].ptr_index] = map[i].buffer;
+		}
+	}
+	return gen;
+}
--- a/genesis.h	Fri Jan 04 19:13:47 2019 -0800
+++ b/genesis.h	Sat Jan 05 00:58:08 2019 -0800
@@ -31,6 +31,7 @@
 	uint16_t        *lock_on;
 	uint16_t        *work_ram;
 	uint8_t         *zram;
+	void            *expansion;
 	void            *extra;
 	uint8_t         *save_storage;
 	void            *mapper_temp;
@@ -62,6 +63,7 @@
 
 m68k_context * sync_components(m68k_context *context, uint32_t address);
 genesis_context *alloc_config_genesis(void *rom, uint32_t rom_size, void *lock_on, uint32_t lock_on_size, uint32_t system_opts, uint8_t force_region);
+genesis_context *alloc_config_genesis_cdboot(system_media *media, uint32_t system_opts, uint8_t force_region);
 void genesis_serialize(genesis_context *gen, serialize_buffer *buf, uint32_t m68k_pc);
 void genesis_deserialize(deserialize_buffer *buf, genesis_context *gen);
 
--- a/romdb.c	Fri Jan 04 19:13:47 2019 -0800
+++ b/romdb.c	Sat Jan 05 00:58:08 2019 -0800
@@ -36,11 +36,14 @@
 	return "SRAM";
 }
 
-tern_node *load_rom_db()
+tern_node *get_rom_db()
 {
-	tern_node *db = parse_bundled_config("rom.db");
+	static tern_node *db;
 	if (!db) {
-		fatal_error("Failed to load ROM DB\n");
+		db = parse_bundled_config("rom.db");
+		if (!db) {
+			fatal_error("Failed to load ROM DB\n");
+		}
 	}
 	return db;
 }
--- a/romdb.h	Fri Jan 04 19:13:47 2019 -0800
+++ b/romdb.h	Sat Jan 05 00:58:08 2019 -0800
@@ -81,7 +81,7 @@
 #define GAME_ID_OFF 0x183
 #define GAME_ID_LEN 8
 
-tern_node *load_rom_db();
+tern_node *get_rom_db();
 rom_info configure_rom(tern_node *rom_db, void *vrom, uint32_t rom_size, void *lock_on, uint32_t lock_on_size, memmap_chunk const *base_map, uint32_t base_chunks);
 rom_info configure_rom_heuristics(uint8_t *rom, uint32_t rom_size, memmap_chunk const *base_map, uint32_t base_chunks);
 uint8_t translate_region_char(uint8_t c);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/segacd.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,263 @@
+#include <stdlib.h>
+#include <string.h>
+#include "segacd.h"
+#include "genesis.h"
+#include "util.h"
+
+static void *prog_ram_wp_write16(uint32_t address, void *vcontext, uint16_t value)
+{
+	return vcontext;
+}
+
+static void *prog_ram_wp_write8(uint32_t address, void *vcontext, uint8_t value)
+{
+	return vcontext;
+}
+
+static uint16_t work_ram_2M_read16(uint32_t address, void *vcontext)
+{
+	return 0;
+}
+
+static uint8_t work_ram_2M_read8(uint32_t address, void *vcontext)
+{
+	return 0;
+}
+
+static void *work_ram_2M_write16(uint32_t address, void *vcontext, uint16_t value)
+{
+	return vcontext;
+}
+
+static void *work_ram_2M_write8(uint32_t address, void *vcontext, uint8_t value)
+{
+	return vcontext;
+}
+
+static uint16_t work_ram_1M_read16(uint32_t address, void *vcontext)
+{
+	return 0;
+}
+
+static uint8_t work_ram_1M_read8(uint32_t address, void *vcontext)
+{
+	return 0;
+}
+
+static void *work_ram_1M_write16(uint32_t address, void *vcontext, uint16_t value)
+{
+	return vcontext;
+}
+
+static void *work_ram_1M_write8(uint32_t address, void *vcontext, uint8_t value)
+{
+	return vcontext;
+}
+
+static uint8_t pcm_read8(uint32_t address, void *vcontext)
+{
+	return 0;
+}
+
+static uint16_t pcm_read16(uint32_t address, void *vcontext)
+{
+	return 0xFF00 | pcm_read8(address+1, vcontext);
+}
+
+static void *pcm_write8(uint32_t address, void *vcontext, uint8_t value)
+{
+	return vcontext;
+}
+
+static void *pcm_write16(uint32_t address, void *vcontext, uint16_t value)
+{
+	return pcm_write8(address+1, vcontext, value);
+}
+
+static uint16_t sub_gate_read16(uint32_t address, void *vcontext)
+{
+	m68k_context *m68k = vcontext;
+	segacd_context *cd = m68k->system;
+	return cd->gate_array[(address & 0x1FF) >> 1];
+}
+
+static uint8_t sub_gate_read8(uint32_t address, void *vcontext)
+{
+	m68k_context *m68k = vcontext;
+	segacd_context *cd = m68k->system;
+	uint16_t val = cd->gate_array[(address & 0x1FF) >> 1];
+	return address & 1 ? val : val >> 8;
+}
+
+static void *sub_gate_write16(uint32_t address, void *vcontext, uint16_t value)
+{
+	m68k_context *m68k = vcontext;
+	segacd_context *cd = m68k->system;
+	uint32_t reg = (address & 0x1FF) >> 1;
+	switch (reg)
+	{
+	case 0x7:
+		cd->gate_array[reg] &= 0xFF00;
+		cd->gate_array[reg] |= value & 0xFF;
+		break;
+	case 0x10:
+	case 0x11:
+	case 0x12:
+	case 0x13:
+	case 0x14:
+	case 0x15:
+	case 0x16:
+	case 0x17:
+		//no effects for these other than saving the value
+		cd->gate_array[reg] = value;
+		break;
+	default:
+		printf("Unhandled gate array write %X:%X\n", address, value);
+	}
+	return vcontext;
+}
+
+static void *sub_gate_write8(uint32_t address, void *vcontext, uint8_t value)
+{
+	m68k_context *m68k = vcontext;
+	segacd_context *cd = m68k->system;
+	uint32_t reg = (address & 0x1FF) >> 1;
+	uint16_t value16;
+	if (address & 1) {
+		value16 = cd->gate_array[reg] & 0xFF00 | value;
+	} else {
+		value16 = cd->gate_array[reg] & 0xFF | (value << 8);
+	}
+	return sub_gate_write16(address, vcontext, value16);
+}
+
+static uint16_t main_gate_read16(uint32_t address, void *vcontext)
+{
+	m68k_context *m68k = vcontext;
+	segacd_context *cd = m68k->system;
+	return cd->gate_array[(address & 0x1FF) >> 1];
+}
+
+static uint8_t main_gate_read8(uint32_t address, void *vcontext)
+{
+	m68k_context *m68k = vcontext;
+	segacd_context *cd = m68k->system;
+	uint16_t val = cd->gate_array[(address & 0x1FF) >> 1];
+	return address & 1 ? val : val >> 8;
+}
+
+static void *main_gate_write16(uint32_t address, void *vcontext, uint16_t value)
+{
+	m68k_context *m68k = vcontext;
+	genesis_context *gen = m68k->system;
+	segacd_context *cd = gen->expansion;
+	uint32_t reg = (address & 0x1FF) >> 1;
+	switch (reg)
+	{
+	case 0x7:
+		cd->gate_array[reg] &= 0xFF;
+		cd->gate_array[reg] |= value & 0xFF00;
+		break;
+	case 0x8:
+	case 0x9:
+	case 0xA:
+	case 0xB:
+	case 0xC:
+	case 0xD:
+	case 0xE:
+	case 0xF:
+		//no effects for these other than saving the value
+		cd->gate_array[reg] = value;
+		break;
+	default:
+		printf("Unhandled gate array write %X:%X\n", address, value);
+	}
+	return vcontext;
+}
+
+static void *main_gate_write8(uint32_t address, void *vcontext, uint8_t value)
+{
+	m68k_context *m68k = vcontext;
+	genesis_context *gen = m68k->system;
+	segacd_context *cd = gen->expansion;
+	uint32_t reg = (address & 0x1FF) >> 1;
+	uint16_t value16;
+	if (address & 1) {
+		value16 = cd->gate_array[reg] & 0xFF00 | value;
+	} else {
+		value16 = cd->gate_array[reg] & 0xFF | (value << 8);
+	}
+	return main_gate_write16(address, vcontext, value16);
+}
+
+segacd_context *alloc_configure_segacd(system_media *media, uint32_t opts, uint8_t force_region, rom_info *info)
+{
+	static memmap_chunk sub_cpu_map[] = {
+		{0x000000, 0x00FEFF, 0x0000, .flags=MMAP_READ | MMAP_CODE, .write_16 = prog_ram_wp_write16, .write_8 = prog_ram_wp_write8},
+		{0x00FF00, 0x07FFFF, 0x0000, .flags=MMAP_READ | MMAP_WRITE | MMAP_CODE},
+		{0x080000, 0x0BFFFF, 0x0000, .flags=MMAP_READ | MMAP_WRITE | MMAP_CODE | MMAP_PTR_IDX | MMAP_FUNC_NULL, .ptr_index = 0,
+			.read_16 = work_ram_2M_read16, .write_16 = work_ram_2M_write16, .read_8 = work_ram_2M_read8, .write_8 = work_ram_2M_write8},
+		{0x0C0000, 0x0DFFFF, 0x0000, .flags=MMAP_READ | MMAP_WRITE | MMAP_CODE | MMAP_PTR_IDX | MMAP_FUNC_NULL, .ptr_index = 1,
+			.read_16 = work_ram_1M_read16, .write_16 = work_ram_1M_write16, .read_8 = work_ram_1M_read8, .write_8 = work_ram_1M_write8},
+		{0xFE0000, 0xFEFFFF, 0x3FFF, .flags=MMAP_READ | MMAP_WRITE | MMAP_ONLY_ODD},
+		{0xFF0000, 0xFF7FFF, 0x0000, .read_16 = pcm_read16, .write_16 = pcm_write16, .read_8 = pcm_read8, .write_8 = pcm_write8},
+		{0xFF8000, 0xFF81FF, 0x0000, .read_16 = sub_gate_read16, .write_16 = sub_gate_write16, .read_8 = sub_gate_read8, .write_8 = sub_gate_write8}
+	};
+	memset(info, 0, sizeof(*info));
+	segacd_context *cd = calloc(sizeof(segacd_context), 1);
+	FILE *f = fopen("cdbios.bin", "rb");
+	if (!f) {
+		fatal_error("Failed to open CD firmware for reading");
+	}
+	long firmware_size = file_size(f);
+	uint32_t adjusted_size = nearest_pow2(firmware_size);
+	cd->rom = malloc(adjusted_size);
+	if (firmware_size != fread(cd->rom, 1, firmware_size, f)) {
+		fatal_error("Failed to read CD firmware");
+	}
+	cd->rom_mut = malloc(adjusted_size);
+	byteswap_rom(adjusted_size, cd->rom);
+	memcpy(cd->rom_mut, cd->rom, adjusted_size);
+	
+	tern_node *db = get_rom_db();
+	*info = configure_rom(db, media->buffer, media->size, media->chain ? media->chain->buffer : NULL, media->chain ? media->chain->size : 0, NULL, 0);
+	
+	cd->prog_ram = malloc(512*1024);
+	cd->work_ram = malloc(256*1024);
+	cd->pcm_ram = malloc(64*1024);
+	//TODO: Load state from file
+	cd->bram = malloc(8*1024);
+	
+	sub_cpu_map[0].buffer = sub_cpu_map[1].buffer = cd->prog_ram;
+	sub_cpu_map[4].buffer = cd->bram;
+	m68k_options *mopts = malloc(sizeof(m68k_options));
+	init_m68k_opts(mopts, sub_cpu_map, sizeof(sub_cpu_map) / sizeof(*sub_cpu_map), 4);
+	cd->m68k = init_68k_context(mopts, NULL);
+	cd->m68k->system = cd;
+	cd->busreq = 1;
+	cd->busack = 1;
+	
+	return cd;
+}
+
+memmap_chunk *segacd_main_cpu_map(segacd_context *cd, uint32_t *num_chunks)
+{
+	static memmap_chunk main_cpu_map[] = {
+		{0x000000, 0x01FFFF, 0xFFFFFF, .flags=MMAP_READ},
+		{0x020000, 0x03FFFF, 0x1FFFF, .flags=MMAP_READ|MMAP_WRITE|MMAP_PTR_IDX|MMAP_FUNC_NULL, .ptr_index = 0},//TODO: support running main CPU code from here
+		{0x040000, 0x05FFFF, 0x1FFFF, .flags=MMAP_READ}, //first ROM alias
+		//TODO: additional ROM/prog RAM aliases
+		{0x200000, 0x01FFFF, 0x1FFFF, .flags=MMAP_READ|MMAP_WRITE|MMAP_PTR_IDX|MMAP_FUNC_NULL, .ptr_index = 1},
+		{0x220000, 0x03FFFF, 0x1FFFF, .flags=MMAP_READ|MMAP_WRITE|MMAP_PTR_IDX|MMAP_FUNC_NULL, .ptr_index = 2},
+		{0xA12000, 0xA12FFF, 0xFFFFFF, .read_16 = main_gate_read16, .write_16 = main_gate_write16, .read_8 = main_gate_read8, .write_8 = main_gate_write8}
+	};
+	//TODO: support cart boot maps
+	//TODO: support BRAM cart
+	main_cpu_map[0].buffer = cd->rom_mut;
+	main_cpu_map[2].buffer = cd->rom;
+	main_cpu_map[1].buffer = cd->prog_ram;
+	main_cpu_map[3].buffer = cd->work_ram;
+	main_cpu_map[3].buffer = cd->work_ram + 0x10000;
+	*num_chunks = sizeof(main_cpu_map) / sizeof(*main_cpu_map);
+	return main_cpu_map;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/segacd.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,25 @@
+#ifndef SEGACD_H_
+#define SEGACD_H_
+#include <stdint.h>
+#include "system.h"
+#include "m68k_core.h"
+
+typedef struct {
+	m68k_context *m68k;
+	system_media *media;
+	uint16_t     gate_array[0x100];
+	uint8_t      busreq;
+	uint8_t      busack;
+	uint8_t      reset;
+	uint16_t     *rom;     //unaltered ROM, needed for mirrored locations
+	uint16_t     *rom_mut; //ROM with low 16-bit of HINT vector modified by register write
+	uint16_t     *prog_ram;
+	uint16_t     *work_ram;
+	uint8_t      *pcm_ram;
+	uint8_t      *bram;
+} segacd_context;
+
+segacd_context *alloc_configure_segacd(system_media *media, uint32_t opts, uint8_t force_region, rom_info *info);
+memmap_chunk *segacd_main_cpu_map(segacd_context *cd, uint32_t *num_chunks);
+
+#endif //SEGACD_H_
--- a/system.c	Fri Jan 04 19:13:47 2019 -0800
+++ b/system.c	Sat Jan 05 00:58:08 2019 -0800
@@ -12,7 +12,11 @@
 system_type detect_system_type(system_media *media)
 {
 	if (safe_cmp("SEGA", 0x100, media->buffer, media->size)) {
-		//TODO: Differentiate between vanilla Genesis and Sega CD/32X games
+		//TODO: support other bootable identifiers
+		if (safe_cmp("SEGADISCSYSTEM", 0, media->buffer, media->size)) {
+			return SYSTEM_SEGACD;
+		}
+		//TODO: Differentiate between vanilla Genesis and 32X games
 		return SYSTEM_GENESIS;
 	}
 	if (safe_cmp("TMR SEGA", 0x1FF0, media->buffer, media->size)
@@ -60,6 +64,8 @@
 	{
 	case SYSTEM_GENESIS:
 		return &(alloc_config_genesis(media->buffer, media->size, lock_on, lock_on_size, opts, force_region))->header;
+	case SYSTEM_SEGACD:
+		return &(alloc_config_genesis_cdboot(media, opts, force_region))->header;
 #ifndef NO_Z80
 	case SYSTEM_SMS:
 		return &(alloc_configure_sms(media, opts, force_region))->header;
--- a/system.h	Fri Jan 04 19:13:47 2019 -0800
+++ b/system.h	Sat Jan 05 00:58:08 2019 -0800
@@ -8,6 +8,7 @@
 typedef enum {
 	SYSTEM_UNKNOWN,
 	SYSTEM_GENESIS,
+	SYSTEM_SEGACD,
 	SYSTEM_SMS,
 	SYSTEM_JAGUAR
 } system_type;
@@ -66,6 +67,11 @@
 	system_type       type;
 };
 
+typedef enum {
+	MEDIA_CART,
+	MEDIA_CDROM
+} media_type;
+
 struct system_media {
 	void         *buffer;
 	char         *dir;
@@ -73,6 +79,7 @@
 	char         *extension;
 	system_media *chain;
 	uint32_t     size;
+	media_type   type;
 };
 
 #define OPT_ADDRESS_LOG (1U << 31U)