changeset 2053:3414a4423de1 segacd

Merge from default
author Michael Pavone <pavone@retrodev.com>
date Sat, 15 Jan 2022 13:15:21 -0800
parents 5dacaef602a7 (diff) 3748a2a8a4b7 (current diff)
children 8ee7ecbf3f21
files Makefile blastem.c genesis.c genesis.h romdb.c romdb.h system.c system.h
diffstat 10 files changed, 496 insertions(+), 127 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Sat Jan 01 18:54:46 2022 -0800
+++ b/Makefile	Sat Jan 15 13:15:21 2022 -0800
@@ -214,11 +214,11 @@
 
 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 gen_player.o
+	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o zip.o bindings.o jcart.o gen_player.o segacd.o
 
 LIBOBJS=libblastem.o system.o genesis.o debug.o gdb_remote.o vdp.o io.o romdb.o hash.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 jcart.o rom.db.o gen_player.o $(LIBZOBJS)
+	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o jcart.o rom.db.o gen_player.o segacd.o $(LIBZOBJS)
 	
 ifdef NONUKLEAR
 CFLAGS+= -DDISABLE_NUKLEAR
@@ -339,7 +339,7 @@
 
 vos_prog_info : vos_prog_info.o vos_program_module.o
 	$(CC) -o vos_prog_info vos_prog_info.o vos_program_module.o
-	
+
 m68k.c : m68k.cpu cpu_dsl.py
 	./cpu_dsl.py -d call $< > $@
 
--- a/blastem.c	Sat Jan 01 18:54:46 2022 -0800
+++ b/blastem.c	Sat Jan 15 13:15:21 2022 -0800
@@ -91,7 +91,7 @@
 	size_t readsize = 0;
 	uint16_t *dst, *buf;
 	dst = buf = malloc(filesize);
-	
+
 
 	size_t read;
 	do {
@@ -107,9 +107,9 @@
 		}
 	} while(read > 0);
 	romclose(f);
-	
+
 	*buffer = buf;
-	
+
 	return readsize;
 }
 
@@ -132,7 +132,7 @@
 	return 0;
 }
 
-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", "smd"};
 	const uint32_t num_exts = sizeof(valid_exts)/sizeof(*valid_exts);
@@ -140,7 +140,7 @@
 	if (!z) {
 		return 0;
 	}
-	
+
 	for (uint32_t i = 0; i < z->num_entries; i++)
 	{
 		char *ext = path_extension(z->entries[i].name);
@@ -151,20 +151,23 @@
 		{
 			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) {
-					if (is_smd_format(z->entries[i].name, *dst)) {
+				dst->buffer = zip_read(z, i, &out_size);
+				if (dst->buffer) {
+					if (is_smd_format(z->entries[i].name, dst->buffer)) {
 						size_t offset;
 						for (offset = 0; offset + SMD_BLOCK_SIZE + SMD_HEADER_SIZE <= out_size; offset += SMD_BLOCK_SIZE)
 						{
 							uint8_t tmp[SMD_BLOCK_SIZE];
-							uint8_t *u8dst = *dst;
+							uint8_t *u8dst = dst->buffer;
 							memcpy(tmp, u8dst + offset + SMD_HEADER_SIZE, SMD_BLOCK_SIZE);
 							process_smd_block((void *)(u8dst + offset), tmp, SMD_BLOCK_SIZE);
 						}
 						out_size = offset;
 					}
-					free(ext);
+					dst->extension = ext;
+					dst->dir = path_dirname(filename);
+					dst->name = basename_no_extension(filename);
+					dst->size = out_size;
 					zip_close(z);
 					return out_size;
 				}
@@ -176,13 +179,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");
@@ -192,42 +195,49 @@
 	if (sizeof(header) != romread(header, 1, sizeof(header), f)) {
 		fatal_error("Error reading from %s\n", filename);
 	}
-	
+
+	uint32_t ret = 0;
 	if (is_smd_format(filename, header)) {
-		if (stype) {
-			*stype = SYSTEM_GENESIS;
+			if (stype) {
+				*stype = SYSTEM_GENESIS;
+			}
+			ret = load_smd_rom(f, &dst->buffer);
 		}
-		return load_smd_rom(f, dst);
-	}
-	
-	size_t filesize = 512 * 1024;
-	size_t readsize = sizeof(header);
-		
-	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;
+
+	if (!ret) {
+		size_t filesize = 512 * 1024;
+		size_t readsize = sizeof(header);
+
+		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;
+					}
 				}
 			}
-		}
-	} 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;
 }
 
 
@@ -389,10 +399,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;
@@ -411,16 +418,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;
 	}
@@ -572,12 +577,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;
 			}
@@ -611,12 +613,9 @@
 			if (reader_port) {
 				reader_addr = argv[i];
 			} else {
-				if (!(cart.size = load_rom(argv[i], &cart.buffer, 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]);
+			if (!load_media(argv[i], &cart, stype == SYSTEM_UNKNOWN ? &stype : NULL)) {
+				fatal_error("Failed to open %s for reading\n", argv[i]);
+			}
 			}
 			romfname = argv[i];
 			loaded = 1;
@@ -626,7 +625,7 @@
 			height = atoi(argv[i]);
 		}
 	}
-	
+
 	int def_width = 0, def_height = 0;
 	char *config_width = tern_find_path(config, "video\0width\0", TVAL_PTR).ptrval;
 	if (config_width) {
@@ -657,7 +656,7 @@
 		render_set_drag_drop_handler(on_drag_drop);
 	}
 	set_bindings();
-	
+
 	uint8_t menu = !loaded;
 	uint8_t use_nuklear = 0;
 #ifndef DISABLE_NUKLEAR
@@ -670,7 +669,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 {
@@ -683,12 +682,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;
@@ -705,12 +704,12 @@
 		if (stype == SYSTEM_UNKNOWN) {
 			fatal_error("Failed to detect system type for %s\n", romfname);
 		}
-		
+
 		current_system = alloc_config_system(stype, &cart, menu ? 0 : opts, force_region);
 		if (!current_system) {
 			fatal_error("Failed to configure emulated machine for %s\n", romfname);
 		}
-	
+
 		setup_saves(&cart, current_system);
 		update_title(current_system->info.name);
 		if (menu) {
@@ -719,7 +718,7 @@
 			game_system = current_system;
 		}
 	}
-	
+
 #ifndef DISABLE_NUKLEAR
 	if (use_nuklear) {
 		blastem_nuklear_init(!menu);
@@ -740,7 +739,7 @@
 		setup_saves(&cart, current_system);
 		update_title(current_system->info.name);
 	}
-	
+
 	current_system->debugger_type = dtype;
 	current_system->enter_debugger = start_in_debugger && menu == debug_target;
 	current_system->start_context(current_system,  menu ? NULL : statefile);
--- a/genesis.c	Sat Jan 01 18:54:46 2022 -0800
+++ b/genesis.c	Sat Jan 15 13:15:21 2022 -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>
@@ -1410,7 +1411,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);
@@ -1747,7 +1749,7 @@
 	return context;
 }
 
-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 },
@@ -1756,6 +1758,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;
@@ -1831,10 +1843,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->z80->mem_pointers[1] = gen->z80->mem_pointers[2] = NULL;
 
-	gen->cart = main_rom;
-	gen->lock_on = lock_on;
 	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))
 	{
@@ -1864,9 +1874,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) {
@@ -1887,6 +1908,9 @@
 
 	gen->mapper_start_index = rom->mapper_start_index;
 
+	tern_node *model = get_model(config, SYSTEM_GENESIS);
+	uint8_t tmss = !strcmp(tern_find_ptr_default(model, "tmss", "off"), "on");
+
 	//This must happen before we generate memory access functions in init_m68k_opts
 	uint8_t next_ptr_index = 0;
 	uint32_t tmss_min_alloc = 16 * 1024;
@@ -2055,8 +2079,6 @@
 	return gen;
 }
 
-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},
@@ -2070,11 +2092,12 @@
 		           (read_16_fun)unused_read,    (write_16_fun)unused_write,
 		           (read_8_fun)unused_read_b,   (write_8_fun)unused_write_b}
 	};
-	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]));
+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)
+{
+	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
@@ -2093,3 +2116,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	Sat Jan 01 18:54:46 2022 -0800
+++ b/genesis.h	Sat Jan 15 13:15:21 2022 -0800
@@ -35,6 +35,7 @@
 	uint16_t        *lock_on;
 	uint16_t        *work_ram;
 	uint8_t         *zram;
+	void            *expansion;
 	void            *extra;
 	uint8_t         *save_storage;
 	void            *mapper_temp;
@@ -82,6 +83,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, uint8_t all);
 void genesis_deserialize(deserialize_buffer *buf, genesis_context *gen);
 
--- a/romdb.c	Sat Jan 01 18:54:46 2022 -0800
+++ b/romdb.c	Sat Jan 15 13:15:21 2022 -0800
@@ -38,11 +38,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	Sat Jan 01 18:54:46 2022 -0800
+++ b/romdb.h	Sat Jan 15 13:15:21 2022 -0800
@@ -84,7 +84,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 15 13:15:21 2022 -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 15 13:15:21 2022 -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	Sat Jan 01 18:54:46 2022 -0800
+++ b/system.c	Sat Jan 15 13:15:21 2022 -0800
@@ -13,7 +13,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)
@@ -71,6 +75,8 @@
 		return &(alloc_config_genesis(media->buffer, media->size, lock_on, lock_on_size, opts, force_region))->header;
 	case SYSTEM_GENESIS_PLAYER:
 		return &(alloc_config_gen_player(media->buffer, media->size))->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	Sat Jan 01 18:54:46 2022 -0800
+++ b/system.h	Sat Jan 15 13:15:21 2022 -0800
@@ -10,6 +10,7 @@
 	SYSTEM_UNKNOWN,
 	SYSTEM_GENESIS,
 	SYSTEM_GENESIS_PLAYER,
+	SYSTEM_SEGACD,
 	SYSTEM_SMS,
 	SYSTEM_SMS_PLAYER,
 	SYSTEM_JAGUAR,
@@ -38,46 +39,51 @@
 #include "event_log.h"
 
 struct system_header {
-	system_header           *next_context;
-	system_str_fun          start_context;
-	system_fun              resume_context;
-	system_fun              load_save;
-	system_fun              persist_save;
-	system_u8_fun_r8        load_state;
-	system_fun              request_exit;
-	system_fun              soft_reset;
-	system_fun              free_context;
-	system_fun_r16          get_open_bus_value;
-	system_u32_fun          set_speed_percent;
-	system_fun              inc_debug_mode;
-	system_u8_u8_fun        gamepad_down;
-	system_u8_u8_fun        gamepad_up;
-	system_u8_u8_fun        mouse_down;
-	system_u8_u8_fun        mouse_up;
-	system_mabs_fun         mouse_motion_absolute;
-	system_mrel_fun         mouse_motion_relative;
-	system_u8_fun           keyboard_down;
-	system_u8_fun           keyboard_up;
-	system_fun              config_updated;
+	system_header     *next_context;
+	system_str_fun    start_context;
+	system_fun        resume_context;
+	system_fun        load_save;
+	system_fun        persist_save;
+	system_u8_fun_r8  load_state;
+	system_fun        request_exit;
+	system_fun        soft_reset;
+	system_fun        free_context;
+	system_fun_r16    get_open_bus_value;
+	system_u32_fun    set_speed_percent;
+	system_fun        inc_debug_mode;
+	system_u8_u8_fun  gamepad_down;
+	system_u8_u8_fun  gamepad_up;
+	system_u8_u8_fun  mouse_down;
+	system_u8_u8_fun  mouse_up;
+	system_mabs_fun   mouse_motion_absolute;
+	system_mrel_fun   mouse_motion_relative;
+	system_u8_fun     keyboard_down;
+	system_u8_fun     keyboard_up;
+	system_fun        config_updated;
 	system_ptrszt_fun_rptr8 serialize;
 	system_ptr8_sizet_fun   deserialize;
 	system_str_fun          start_vgm_log;
 	system_fun              stop_vgm_log;
-	rom_info                info;
-	arena                   *arena;
-	char                    *next_rom;
-	char                    *save_dir;
-	uint8_t                 enter_debugger;
-	uint8_t                 should_exit;
-	uint8_t                 save_state;
-	uint8_t                 delayed_load_slot;
-	uint8_t                 has_keyboard;
+	rom_info          info;
+	arena             *arena;
+	char              *next_rom;
+	char              *save_dir;
+	uint8_t           enter_debugger;
+	uint8_t           should_exit;
+	uint8_t           save_state;
+	uint8_t           delayed_load_slot;
+	uint8_t           has_keyboard;
 	uint8_t                 vgm_logging;
 	uint8_t                 force_release;
-	debugger_type           debugger_type;
-	system_type             type;
+	debugger_type     debugger_type;
+	system_type       type;
 };
 
+typedef enum {
+	MEDIA_CART,
+	MEDIA_CDROM
+} media_type;
+
 struct system_media {
 	void         *buffer;
 	char         *dir;
@@ -85,6 +91,7 @@
 	char         *extension;
 	system_media *chain;
 	uint32_t     size;
+	media_type   type;
 };
 
 #define OPT_ADDRESS_LOG (1U << 31U)