changeset 2072:cc13c100b027

Merge Sega CD branch now that it sort of works
author Michael Pavone <pavone@retrodev.com>
date Sun, 30 Jan 2022 22:29:29 -0800
parents 3748a2a8a4b7 (current diff) 598017ef4b0d (diff)
children c69e42444f96
files
diffstat 31 files changed, 4140 insertions(+), 642 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Sat Jan 01 18:54:46 2022 -0800
+++ b/Makefile	Sun Jan 30 22:29:29 2022 -0800
@@ -181,7 +181,7 @@
 M68KOBJS=68kinst.o
 
 ifdef NEW_CORE
-Z80OBJS=z80.o z80inst.o 
+Z80OBJS=z80.o z80inst.o
 M68KOBJS+= m68k.o
 CFLAGS+= -DNEW_CORE
 else
@@ -197,7 +197,7 @@
 endif
 endif
 AUDIOOBJS=ym2612.o psg.o wave.o vgm.o event_log.o render_audio.o
-CONFIGOBJS=config.o tern.o util.o paths.o 
+CONFIGOBJS=config.o tern.o util.o paths.o
 NUKLEAROBJS=$(FONT) nuklear_ui/blastem_nuklear.o nuklear_ui/sfnt.o
 RENDEROBJS=ppm.o controller_info.o
 ifdef USE_FBDEV
@@ -205,7 +205,7 @@
 else
 RENDEROBJS+= render_sdl.o
 endif
-	
+
 ifdef NOZLIB
 CFLAGS+= -DDISABLE_ZLIB
 else
@@ -214,12 +214,14 @@
 
 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 lc8951.o cue.o cdd_mcu.o cd_graphics.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 $(LIBZOBJS) \
+	segacd.o lc8951.o cue.o cdd_mcu.o cd_graphics.o
+
 ifdef NONUKLEAR
 CFLAGS+= -DDISABLE_NUKLEAR
 else
@@ -278,7 +280,7 @@
 blastem$(EXE) : $(MAINOBJS)
 	$(CC) -o $@ $^ $(LDFLAGS) $(PROFFLAGS)
 	$(FIXUP) ./$@
-	
+
 blastjag$(EXE) : jaguar.o jag_video.o $(RENDEROBJS) serialize.o $(M68KOBJS) $(TRANSOBJS) $(CONFIGOBJS)
 	$(CC) -o $@ $^ $(LDFLAGS)
 
@@ -287,7 +289,7 @@
 
 dis$(EXE) : dis.o 68kinst.o tern.o vos_program_module.o
 	$(CC) -o $@ $^ $(OPT)
-	
+
 jagdis : jagdis.o jagcpu.o tern.o
 	$(CC) -o $@ $^
 
@@ -327,7 +329,7 @@
 
 test_arm : test_arm.o gen_arm.o mem.o gen.o
 	$(CC) -o test_arm test_arm.o gen_arm.o mem.o gen.o
-	
+
 test_int_timing : test_int_timing.o vdp.o
 	$(CC) -o $@ $^
 
@@ -339,7 +341,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 $< > $@
 
@@ -354,7 +356,7 @@
 
 %.o : %.c
 	$(CC) $(CFLAGS) -c -o $@ $<
-  
+
 %.o : %.m
 	$(CC) $(CFLAGS) -c -o $@ $<
 
--- a/backend_x86.c	Sat Jan 01 18:54:46 2022 -0800
+++ b/backend_x86.c	Sun Jan 30 22:29:29 2022 -0800
@@ -116,7 +116,7 @@
 	if (after_inc) {
 		*after_inc = code->cur;
 	}
-	
+
 	if (opts->address_size == SZ_D && opts->address_mask != 0xFFFFFFFF) {
 		and_ir(code, opts->address_mask, adr_reg, SZ_D);
 	} else if (opts->address_size == SZ_W && opts->address_mask != 0xFFFF) {
@@ -127,19 +127,31 @@
 	uint32_t ram_flags_off = opts->ram_flags_off;
 	uint32_t min_address = 0;
 	uint32_t max_address = opts->max_address;
+	uint8_t need_wide_jcc = 0;
 	for (uint32_t chunk = 0; chunk < num_chunks; chunk++)
 	{
+		code_info chunk_start = *code;
 		if (memmap[chunk].start > min_address) {
 			cmp_ir(code, memmap[chunk].start, adr_reg, opts->address_size);
 			lb_jcc = code->cur + 1;
-			jcc(code, CC_C, code->cur + 2);
+			if (need_wide_jcc) {
+				jcc(code, CC_C, code->cur + 130);
+				lb_jcc++;
+			} else {
+				jcc(code, CC_C, code->cur + 2);
+			}
 		} else {
 			min_address = memmap[chunk].end;
 		}
 		if (memmap[chunk].end < max_address) {
 			cmp_ir(code, memmap[chunk].end, adr_reg, opts->address_size);
 			ub_jcc = code->cur + 1;
-			jcc(code, CC_NC, code->cur + 2);
+			if (need_wide_jcc) {
+				jcc(code, CC_NC, code->cur + 130);
+				ub_jcc++;
+			} else {
+				jcc(code, CC_NC, code->cur + 2);
+			}
 		} else {
 			max_address = memmap[chunk].start;
 		}
@@ -298,6 +310,33 @@
 			}
 			retn(code);
 		}
+		if (lb_jcc) {
+			if (need_wide_jcc) {
+				*((int32_t*)lb_jcc) = code->cur - (lb_jcc+4);
+			} else if (code->cur - (lb_jcc+1) > 0x7f) {
+				need_wide_jcc = 1;
+				chunk--;
+				*code = chunk_start;
+				continue;
+			} else {
+				*lb_jcc = code->cur - (lb_jcc+1);
+			}
+			lb_jcc = NULL;
+		}
+		if (ub_jcc) {
+			if (need_wide_jcc) {
+				*((int32_t*)ub_jcc) = code->cur - (ub_jcc+4);
+			} else if (code->cur - (ub_jcc+1) > 0x7f) {
+				need_wide_jcc = 1;
+				chunk--;
+				*code = chunk_start;
+				continue;
+			} else {
+				*ub_jcc = code->cur - (ub_jcc+1);
+			}
+
+			ub_jcc = NULL;
+		}
 		if (memmap[chunk].flags & MMAP_CODE) {
 			uint32_t added_offset;
 			if (memmap[chunk].mask == opts->address_mask) {
@@ -311,13 +350,8 @@
 				ram_flags_off += 1;
 			}
 		}
-		if (lb_jcc) {
-			*lb_jcc = code->cur - (lb_jcc+1);
-			lb_jcc = NULL;
-		}
-		if (ub_jcc) {
-			*ub_jcc = code->cur - (ub_jcc+1);
-			ub_jcc = NULL;
+		if (need_wide_jcc) {
+			need_wide_jcc = 0;
 		}
 	}
 	if (!is_write) {
--- a/blastem.c	Sat Jan 01 18:54:46 2022 -0800
+++ b/blastem.c	Sun Jan 30 22:29:29 2022 -0800
@@ -30,6 +30,7 @@
 #include "bindings.h"
 #include "menu.h"
 #include "zip.h"
+#include "cue.h"
 #include "event_log.h"
 #ifndef DISABLE_NUKLEAR
 #include "nuklear_ui/blastem_nuklear.h"
@@ -91,7 +92,7 @@
 	size_t readsize = 0;
 	uint16_t *dst, *buf;
 	dst = buf = malloc(filesize);
-	
+
 
 	size_t read;
 	do {
@@ -107,9 +108,9 @@
 		}
 	} while(read > 0);
 	romclose(f);
-	
+
 	*buffer = buf;
-	
+
 	return readsize;
 }
 
@@ -132,7 +133,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 +141,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 +152,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 +180,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 +196,56 @@
 	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->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);
+	if (!strcasecmp(dst->extension, "cue")) {
+		if (parse_cue(dst)) {
+			if (stype) {
+				*stype = SYSTEM_SEGACD;
+			}
 		}
-	} while (read > 0);
-	
-	*dst = buf;
-	
-	romclose(f);
-	return readsize;
+	}
+
+	return ret;
 }
 
 
@@ -389,10 +407,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 +426,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 +585,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 +621,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 +633,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 +664,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 +677,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 +690,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 +712,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 +726,7 @@
 			game_system = current_system;
 		}
 	}
-	
+
 #ifndef DISABLE_NUKLEAR
 	if (use_nuklear) {
 		blastem_nuklear_init(!menu);
@@ -740,7 +747,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);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cd_graphics.c	Sun Jan 30 22:29:29 2022 -0800
@@ -0,0 +1,286 @@
+#include "cd_graphics.h"
+#include "backend.h"
+
+void cd_graphics_init(segacd_context *cd)
+{
+	cd->graphics_int_cycle = CYCLE_NEVER;
+}
+
+#define BIT_HFLIP 0x8000
+
+static uint8_t get_src_pixel(segacd_context *cd)
+{
+	uint16_t x = cd->graphics_x >> 11;
+	uint16_t y = cd->graphics_y >> 11;
+	cd->graphics_x += cd->graphics_dx;
+	cd->graphics_x &= 0xFFFFFF;
+	cd->graphics_y += cd->graphics_dy;
+	cd->graphics_y &= 0xFFFFFF;
+	uint16_t stamp_shift, pixel_mask;
+	uint16_t stamp_num_mask;
+	if (cd->gate_array[GA_STAMP_SIZE] & BIT_STS) {
+		//32x32 stamps
+		stamp_shift = 5;
+		pixel_mask = 0x1F;
+		stamp_num_mask = 0x3FC;
+	} else {
+		//16x16 stamps
+		stamp_shift = 4;
+		pixel_mask = 0xF;
+		stamp_num_mask = 0x3FF;
+	}
+	uint16_t stamp_x = x >> stamp_shift;
+	uint16_t stamp_y = y >> stamp_shift;
+	uint16_t max, base_mask;
+	uint32_t row_shift;
+	if (cd->gate_array[GA_STAMP_SIZE] & BIT_SMS) {
+		max = 4096 >> stamp_shift;
+		base_mask = 0xE000 << ((5 - stamp_shift) << 1);
+		//128 stamps in 32x32 mode, 256 stamps in 16x16 mode
+		row_shift = 12 - stamp_shift;
+	} else {
+		max = 256 >> stamp_shift;
+		base_mask = 0xFFE0 << ((5 - stamp_shift) << 1);
+		//8 stamps in 32x32 mode, 16 stamps in 16x16 mode
+		row_shift = 8 - stamp_shift;
+	}
+	if (stamp_x > max || stamp_y > max) {
+		if (cd->gate_array[GA_STAMP_SIZE] & BIT_RPT) {
+			stamp_x &= max - 1;
+			stamp_y &= max - 1;
+		} else {
+			return 0;
+		}
+	}
+	uint32_t address = (cd->gate_array[GA_STAMP_MAP_BASE] & base_mask) << 1;
+	address += (stamp_y << row_shift) + stamp_x;
+	uint16_t stamp_def = cd->word_ram[address];
+	uint16_t stamp_num = stamp_def & stamp_num_mask;
+	if (!stamp_num) {
+		//manual says stamp 0 can't be used, I assume that means it's treated as transparent
+		return 0;
+	}
+	uint16_t pixel_x = x & pixel_mask;
+	uint16_t pixel_y = y & pixel_mask;
+	if (stamp_def & BIT_HFLIP) {
+		pixel_x = pixel_mask - pixel_x;
+	}
+	uint16_t tmp;
+	switch (stamp_def >> 13 & 3)
+	{
+	case 0:
+		break;
+	case 1:
+		tmp = pixel_y;
+		pixel_y = pixel_x;
+		pixel_x = pixel_mask - tmp;
+		break;
+	case 2:
+		tmp = pixel_y;
+		pixel_y = pixel_mask - pixel_x;
+		pixel_x = pixel_mask - tmp;
+		break;
+	case 3:
+		tmp = pixel_y;
+		pixel_y = pixel_mask - pixel_x;
+		pixel_x = tmp;
+		break;
+	}
+	uint16_t cell_x = pixel_x >> 3;
+	uint32_t pixel_address = stamp_num << 6;
+	pixel_address += (pixel_y << 1) + (cell_x << (stamp_shift + 1)) + (pixel_x >> 2 & 1);
+	uint16_t word = cd->word_ram[pixel_address];
+	switch (pixel_x & 3)
+	{
+	default:
+	case 0:
+		return word >> 12;
+	case 1:
+		return word >> 8 & 0xF;
+	case 2:
+		return word >> 4 & 0xF;
+	case 3:
+		return word & 0xF;
+	}
+
+}
+
+enum {
+	FETCH_X,
+	FETCH_Y,
+	FETCH_DX,
+	FETCH_DY,
+	PIXEL0,
+	PIXEL1,
+	PIXEL2,
+	PIXEL3,
+	DRAW
+};
+
+void draw_pixels(segacd_context *cd)
+{
+	uint16_t to_draw = 4 - (cd->graphics_dst_x & 3);
+	uint16_t x_end = cd->gate_array[GA_IMAGE_BUFFER_HDOTS] + (cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 3);
+	if (cd->graphics_dst_x + to_draw > x_end) {
+		to_draw = cd->gate_array[GA_IMAGE_BUFFER_HDOTS] + (cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 3) - cd->graphics_dst_x;
+	}
+	for(uint16_t i = 0; i < to_draw; i++)
+	{
+		uint32_t dst_address = cd->gate_array[GA_IMAGE_BUFFER_START] << 1;
+		dst_address += cd->graphics_dst_y << 1;
+		dst_address += cd->graphics_dst_x >> 2 & 1;
+		dst_address += ((cd->graphics_dst_x >> 3) * cd->gate_array[GA_IMAGE_BUFFER_VCELLS]) << 4;
+		uint16_t pixel_shift = 12 - 4 * (cd->graphics_dst_x & 3);
+		uint16_t pixel = cd->graphics_pixels[i] << pixel_shift;
+		uint16_t src_mask_check = 0xF << pixel_shift;
+		uint16_t src_mask_keep = ~src_mask_check;
+		pixel &= src_mask_check;
+		switch (cd->gate_array[1] >> 3 & 3)
+		{
+		case 0:
+			//priority mode off
+			cd->word_ram[dst_address] &= src_mask_keep;
+			cd->word_ram[dst_address] |= pixel;
+			break;
+		case 1:
+			//underwrite
+			if (pixel && ! (cd->word_ram[dst_address] & src_mask_check)) {
+				cd->word_ram[dst_address] &= src_mask_keep;
+				cd->word_ram[dst_address] |= pixel;
+			}
+			break;
+		case 3:
+			//overwrite
+			if (pixel) {
+				cd->word_ram[dst_address] &= src_mask_keep;
+				cd->word_ram[dst_address] |= pixel;
+			}
+			break;
+		}
+		cd->graphics_dst_x++;
+	}
+	if (cd->graphics_dst_x == x_end) {
+		cd->graphics_dst_y++;
+		--cd->gate_array[GA_IMAGE_BUFFER_LINES];
+		cd->gate_array[GA_TRACE_VECTOR_BASE] += 2;
+		cd->graphics_step = FETCH_X;
+	} else {
+		cd->graphics_step = PIXEL0;
+	}
+}
+
+#define CHECK_CYCLES cd->graphics_step++; if(cd->graphics_cycle >= cycle) break
+#define CHECK_ONLY if(cd->graphics_cycle >= cycle) break
+
+static void do_graphics(segacd_context *cd, uint32_t cycle)
+{
+	if (!cd->gate_array[GA_IMAGE_BUFFER_LINES]) {
+		return;
+	}
+	while (cd->graphics_cycle < cycle)
+	{
+		switch (cd->graphics_step)
+		{
+		case FETCH_X:
+			cd->graphics_x = cd->word_ram[cd->gate_array[GA_TRACE_VECTOR_BASE] << 1] << 8;
+			cd->graphics_cycle += 3*4;
+			cd->graphics_dst_x = cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 3;
+			CHECK_CYCLES;
+		case FETCH_Y:
+			cd->graphics_y = cd->word_ram[(cd->gate_array[GA_TRACE_VECTOR_BASE] << 1) + 1] << 8;
+			cd->graphics_cycle += 2*4;
+			CHECK_CYCLES;
+		case FETCH_DX:
+			cd->graphics_dx = cd->word_ram[(cd->gate_array[GA_TRACE_VECTOR_BASE] << 1) + 2];
+			if (cd->graphics_dx & 0x8000) {
+				cd->graphics_dx |= 0xFF0000;
+			}
+			cd->graphics_cycle += 2*4;
+			CHECK_CYCLES;
+		case FETCH_DY:
+			cd->graphics_dy = cd->word_ram[(cd->gate_array[GA_TRACE_VECTOR_BASE] << 1) + 3];
+			if (cd->graphics_dy & 0x8000) {
+				cd->graphics_dy |= 0xFF0000;
+			}
+			cd->graphics_cycle += 2*4;
+			CHECK_CYCLES;
+		case PIXEL0:
+			cd->graphics_pixels[0] = get_src_pixel(cd);
+			cd->graphics_cycle += 2*4;
+			if ((cd->graphics_dst_x & 3) == 3 || (cd->graphics_dst_x + 1 == cd->gate_array[GA_IMAGE_BUFFER_HDOTS] + (cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 3))) {
+				cd->graphics_step = DRAW;
+				CHECK_ONLY;
+			} else {
+				CHECK_CYCLES;
+			}
+		case PIXEL1:
+			cd->graphics_pixels[1] = get_src_pixel(cd);
+			cd->graphics_cycle += 2*4;
+			if ((cd->graphics_dst_x & 3) == 2 || (cd->graphics_dst_x + 2 == cd->gate_array[GA_IMAGE_BUFFER_HDOTS] + (cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 3))) {
+				cd->graphics_step = DRAW;
+				CHECK_ONLY;
+			} else {
+				CHECK_CYCLES;
+			}
+		case PIXEL2:
+			cd->graphics_pixels[2] = get_src_pixel(cd);
+			cd->graphics_cycle += 2*4;
+			if ((cd->graphics_dst_x & 3) == 1 || (cd->graphics_dst_x + 3 == cd->gate_array[GA_IMAGE_BUFFER_HDOTS] + (cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 3))) {
+				cd->graphics_step = DRAW;
+				CHECK_ONLY;
+			} else {
+				CHECK_CYCLES;
+			}
+		case PIXEL3:
+			cd->graphics_pixels[3] = get_src_pixel(cd);
+			cd->graphics_cycle += 2*4;
+			CHECK_CYCLES;
+		case DRAW:
+			draw_pixels(cd);
+			cd->graphics_cycle += 1*4;
+			if (!cd->gate_array[GA_IMAGE_BUFFER_LINES]) {
+				break;
+			}
+			CHECK_ONLY;
+		}
+	}
+}
+
+void cd_graphics_run(segacd_context *cd, uint32_t cycle)
+{
+	while (cd->graphics_cycle < cycle)
+	{
+		if (cd->gate_array[GA_STAMP_SIZE] & BIT_GRON) {
+			do_graphics(cd, cycle);
+			//end calculation and actual emulated execution time probably don't 100% line up yet
+			//deal with that here for now
+			for(; cd->graphics_cycle < cycle; cd->graphics_cycle += 4)
+			{
+			}
+			if (cd->graphics_cycle >= cd->graphics_int_cycle) {
+				cd->gate_array[GA_STAMP_SIZE] &= ~BIT_GRON;
+				break;
+			}
+		} else {
+			cd->graphics_cycle = cycle;
+		}
+	}
+}
+void cd_graphics_start(segacd_context *cd)
+{
+	if (!(cd->gate_array[GA_STAMP_SIZE] & BIT_GRON)) {
+		printf("grahpics start @ %u\n", cd->graphics_cycle);
+		cd->gate_array[GA_STAMP_SIZE] |= BIT_GRON;
+		//Manual scan is bad, but formula appears to be
+		// vsize * (13 + 2 * hoffset + 9 * (hdots + hoffset - 1))
+		//with an additional 13? cycle setup cost per line
+		uint32_t lines = cd->gate_array[GA_IMAGE_BUFFER_LINES];
+		uint32_t hdots = cd->gate_array[GA_IMAGE_BUFFER_HDOTS];
+		uint32_t hoffset = cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 3;
+		cd->graphics_int_cycle = cd->graphics_cycle + 4 * lines * (13 + 2 * hoffset + 9 * (hdots + hoffset - 1));
+		cd->graphics_dst_y = cd->gate_array[GA_IMAGE_BUFFER_OFFSET] >> 3;
+	} else {
+		printf("graphics start ignored @ %u\n", cd->graphics_cycle);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cd_graphics.h	Sun Jan 30 22:29:29 2022 -0800
@@ -0,0 +1,27 @@
+#ifndef CD_GRAPHICS_H_
+#define CD_GRAPHICS_H_
+
+#include "segacd.h"
+
+enum {
+	GA_STAMP_SIZE = 0x58/2,
+	GA_STAMP_MAP_BASE,
+	GA_IMAGE_BUFFER_VCELLS,
+	GA_IMAGE_BUFFER_START,
+	GA_IMAGE_BUFFER_OFFSET,
+	GA_IMAGE_BUFFER_HDOTS,
+	GA_IMAGE_BUFFER_LINES,
+	GA_TRACE_VECTOR_BASE
+};
+
+//GA_STAMP_SIZE
+#define BIT_GRON 0x8000
+#define BIT_SMS  0x0004
+#define BIT_STS  0x0002
+#define BIT_RPT  0x0001
+
+void cd_graphics_init(segacd_context *cd);
+void cd_graphics_run(segacd_context *cd, uint32_t cycle);
+void cd_graphics_start(segacd_context *cd);
+
+#endif //CD_GRAPHICS_H_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cdd_mcu.c	Sun Jan 30 22:29:29 2022 -0800
@@ -0,0 +1,596 @@
+#include "cdd_mcu.h"
+#include "backend.h"
+
+#define SCD_MCLKS 50000000
+#define CD_BLOCK_CLKS 16934400
+#define CDD_MCU_DIVIDER 8
+#define SECTOR_CLOCKS (CD_BLOCK_CLKS/75)
+#define NIBBLE_CLOCKS (CDD_MCU_DIVIDER * 77)
+#define BYTE_CLOCKS (SECTOR_CLOCKS/2352) // 96
+
+//lead in start max diameter 46 mm
+//program area start max diameter 50 mm
+//difference 4 mm = 4000 um
+//radius difference 2 mm = 2000 um
+//track pitch 1.6 um
+//1250 physical tracks in between
+//linear speed 1.2 m/s - 1.4 m/s
+// 1.3 m = 1300 mm
+// circumference at 46 mm ~ 144.51 mm
+// circumference at 50 mm ~ 157.08 mm
+// avg is 150.795
+// 75 sectors per second
+// 17.3333 mm "typical" length of a sector
+// ~8.7 sectors per track in lead-in area
+#define LEADIN_SECTORS 10875
+
+static uint32_t cd_block_to_mclks(uint32_t cycles)
+{
+	return ((uint64_t)cycles) * ((uint64_t)SCD_MCLKS) / ((uint64_t)CD_BLOCK_CLKS);
+}
+
+static uint32_t mclks_to_cd_block(uint32_t cycles)
+{
+	return ((uint64_t)cycles) * ((uint64_t)CD_BLOCK_CLKS) / ((uint64_t)SCD_MCLKS);
+}
+
+void cdd_mcu_init(cdd_mcu *context, system_media *media)
+{
+	context->next_int_cycle = CYCLE_NEVER;
+	context->last_subcode_cycle = CYCLE_NEVER;
+	context->last_nibble_cycle = CYCLE_NEVER;
+	context->requested_format = SF_NOTREADY;
+	context->media = media;
+	context->current_status_nibble = -1;
+	context->current_cmd_nibble = -1;
+}
+
+enum {
+	GAO_CDD_CTRL,
+	GAO_CDD_STATUS,
+	GAO_CDD_CMD = GAO_CDD_STATUS+5
+};
+
+static uint8_t checksum(uint8_t *vbuffer)
+{
+	uint8_t *buffer = vbuffer;
+	uint8_t sum = 0;
+	for (int i = 0; i < 9; i++)
+	{
+		sum += buffer[i];
+	}
+	return (~sum) & 0xF;
+}
+#define COARSE_SEEK 2200 //made up numbers
+#define FINE_SEEK 10
+static void handle_seek(cdd_mcu *context)
+{
+	//TODO: more realistic seeking behavior
+	if (context->seeking) {
+		if (context->seek_pba == context->head_pba) {
+			context->seeking = 0;
+		} else if (context->seek_pba > context->head_pba) {
+			if (context->seek_pba - context->head_pba >= COARSE_SEEK) {
+				context->head_pba += COARSE_SEEK;
+			} else if (context->seek_pba - context->head_pba >= FINE_SEEK) {
+				context->head_pba += FINE_SEEK;
+			} else {
+				context->head_pba++;
+			}
+		} else {
+			if (context->head_pba - context->seek_pba >= COARSE_SEEK) {
+				context->head_pba -= COARSE_SEEK;
+			} else if (context->head_pba >= FINE_SEEK) {
+				context->head_pba -= FINE_SEEK;
+			} else {
+				context->head_pba = 0;
+			}
+		}
+	}
+}
+
+static void lba_to_status(cdd_mcu *context, uint32_t lba)
+{
+	uint32_t seconds = lba / 75;
+	uint32_t frames = lba % 75;
+	uint32_t minutes = seconds / 60;
+	seconds = seconds % 60;
+	context->status_buffer.b.time.min_high = minutes / 10;
+	context->status_buffer.b.time.min_low = minutes % 10;
+	context->status_buffer.b.time.sec_high = seconds / 10;
+	context->status_buffer.b.time.sec_low = seconds % 10;
+	context->status_buffer.b.time.frame_high = frames / 10;
+	context->status_buffer.b.time.frame_low = frames % 10;
+}
+
+static void update_status(cdd_mcu *context)
+{
+	switch (context->status)
+	{
+	case DS_PLAY:
+		handle_seek(context);
+		if (!context->seeking) {
+			context->head_pba++;
+			uint32_t lba = context->head_pba - LEADIN_SECTORS;
+			for (uint32_t i = 0; i < context->media->num_tracks; i++)
+			{
+				if (lba < context->media->tracks[i].fake_pregap) {
+					context->in_fake_pregap = 1;
+					break;
+				}
+				lba -= context->media->tracks[i].fake_pregap;
+				if (lba < context->media->tracks[i].start_lba) {
+					context->in_fake_pregap = 1;
+					break;
+				}
+				if (lba < context->media->tracks[i].end_lba) {
+					fseek(context->media->f, lba * 2352, SEEK_SET);
+					context->in_fake_pregap = 0;
+					break;
+				}
+			}
+		}
+		break;
+	case DS_PAUSE:
+		handle_seek(context);
+		break;
+	case DS_TOC_READ:
+		handle_seek(context);
+		if (!context->seeking) {
+			context->head_pba++;
+			if (context->media && context->media->type == MEDIA_CDROM && context->media->num_tracks) {
+				if (context->head_pba > 3*context->media->num_tracks + 1) {
+					context->toc_valid = 1;
+					context->seeking = 1;
+					context->seek_pba = LEADIN_SECTORS + context->media->tracks[0].start_lba;
+					context->status = DS_PAUSE;
+				}
+
+			} else {
+				context->status = DS_NO_DISC;
+			}
+		}
+		break;
+
+	}
+	if (context->first_cmd_received) {
+		switch (context->requested_format)
+		{
+		case SF_ABSOLUTE:
+			if (context->toc_valid && context->head_pba >= LEADIN_SECTORS) {
+				lba_to_status(context, context->head_pba - LEADIN_SECTORS);
+				context->status_buffer.format = SF_ABSOLUTE;
+			} else {
+				context->status_buffer.format = SF_NOTREADY;
+			}
+			break;
+		case SF_RELATIVE:
+			if (context->toc_valid && context->head_pba >= LEADIN_SECTORS) {
+				uint32_t lba =context->head_pba - LEADIN_SECTORS;
+				for (uint32_t i = 0; i < context->media->num_tracks; i++)
+				{
+					if (lba < context->media->tracks[i].end_lba) {
+						if (context->media->tracks[i].fake_pregap) {
+							if (lba > context->media->tracks[i].fake_pregap) {
+								lba -= context->media->tracks[i].fake_pregap;
+							} else {
+								//relative time counts down to 0 in pregap
+								lba = context->media->tracks[i].fake_pregap - lba;
+								break;
+							}
+						}
+						if (lba < context->media->tracks[i].start_lba) {
+							//relative time counts down to 0 in pregap
+							lba = context->media->tracks[i].start_lba - lba;
+						} else {
+							lba -= context->media->tracks[i].start_lba;
+						}
+						break;
+					} else if (context->media->tracks[i].fake_pregap) {
+						lba -= context->media->tracks[i].fake_pregap;
+					}
+				}
+				lba_to_status(context, lba);
+				context->status_buffer.format = SF_RELATIVE;
+			} else {
+				context->status_buffer.format = SF_NOTREADY;
+			}
+			break;
+		case SF_TRACK:
+			if (context->toc_valid && context->head_pba >= LEADIN_SECTORS) {
+				uint32_t lba =context->head_pba - LEADIN_SECTORS;
+				uint32_t i;
+				for (i = 0; i < context->media->num_tracks; i++)
+				{
+					if (lba < context->media->tracks[i].end_lba) {
+						if (context->media->tracks[i].fake_pregap) {
+							if (lba > context->media->tracks[i].fake_pregap) {
+								lba -= context->media->tracks[i].fake_pregap;
+							} else {
+								//relative time counts down to 0 in pregap
+								lba = context->media->tracks[i].fake_pregap - lba;
+								break;
+							}
+						}
+						if (lba < context->media->tracks[i].start_lba) {
+							//relative time counts down to 0 in pregap
+							lba = context->media->tracks[i].start_lba - lba;
+						} else {
+							lba -= context->media->tracks[i].start_lba;
+						}
+						break;
+					} else if (context->media->tracks[i].fake_pregap) {
+						lba -= context->media->tracks[i].fake_pregap;
+					}
+				}
+				context->status_buffer.b.track.track_high = (i + 1) / 10;
+				context->status_buffer.b.track.track_low = (i + 1) % 10;
+				if (context->media->tracks[i].type == TRACK_DATA) {
+					context->status_buffer.b.track.control = 4;
+				} else {
+					//TODO: pre-emphasis flag
+					//TODO: copy permitted flag
+					context->status_buffer.b.track.control = 0;
+				}
+				context->status_buffer.b.track.adr = 1;
+				context->status_buffer.format = SF_TRACK;
+			} else {
+				context->status_buffer.format = SF_NOTREADY;
+			}
+			break;
+		case SF_TOCO:
+			if (context->toc_valid) {
+				uint32_t total_fake_pregap = 0;
+				for (uint32_t i = 0; i < context->media->num_tracks; i++)
+				{
+					total_fake_pregap += context->media->tracks[i].fake_pregap;
+				}
+				lba_to_status(context, context->media->tracks[context->media->num_tracks - 1].end_lba + total_fake_pregap);
+				context->status_buffer.format = SF_TOCO;
+			} else {
+				context->status_buffer.format = SF_NOTREADY;
+			}
+			break;
+		case SF_TOCT:
+			if (context->toc_valid) {
+				context->status_buffer.b.toct.first_track_high = 0;
+				context->status_buffer.b.toct.first_track_low = 1;
+				context->status_buffer.b.toct.last_track_high = (context->media->num_tracks + 1) / 10;
+				context->status_buffer.b.toct.last_track_low = (context->media->num_tracks + 1) % 10;
+				context->status_buffer.b.toct.version = 0;
+				context->status_buffer.format = SF_TOCT;
+			} else {
+				context->status_buffer.format = SF_NOTREADY;
+			}
+			break;
+		case SF_TOCN:
+			if (context->toc_valid) {
+				uint32_t lba = context->media->tracks[context->requested_track - 1].start_lba;
+				for (uint32_t i = 0; i < context->requested_track; i++) {
+					lba += context->media->tracks[i].fake_pregap;
+				}
+				lba_to_status(context, lba);
+				if (context->media->tracks[context->requested_track - 1].type == TRACK_DATA) {
+					context->status_buffer.b.tocn.frame_low |= 0x80;
+				}
+				context->status_buffer.b.tocn.track_low = context->requested_track % 10;
+				context->status_buffer.format = SF_TOCN;
+			} else {
+				context->status_buffer.format = SF_NOTREADY;
+			}
+			break;
+		case SF_NOTREADY:
+			memset(&context->status_buffer, 0, sizeof(context->status_buffer) - 1);
+			context->status_buffer.format = SF_NOTREADY;
+			break;
+		}
+		if (context->error_status == DS_STOP) {
+			if (context->requested_format >= SF_TOCO && context->requested_format <= SF_TOCN) {
+				context->status_buffer.status = DS_TOC_READ;
+			} else if (context->seeking) {
+				context->status_buffer.status = DS_SEEK;
+			} else {
+				context->status_buffer.status = context->status;
+			}
+		} else {
+			context->status_buffer.status = context->error_status;
+			context->status_buffer.format = SF_NOTREADY;
+			context->error_status = DS_STOP;
+		}
+		if (context->requested_format != SF_TOCN) {
+			context->status_buffer.b.time.flags = 1; //TODO: populate these
+		}
+	} else {
+		// Did not receive our first command so just send zeroes
+		memset(&context->status_buffer, 0, sizeof(context->status_buffer) - 1);
+	}
+	context->status_buffer.checksum = checksum((uint8_t *)&context->status_buffer);
+	if (context->status_buffer.format != SF_NOTREADY) {
+		printf("CDD Status %X%X.%X%X%X%X%X%X.%X%X\n",
+			context->status_buffer.status, context->status_buffer.format,
+			context->status_buffer.b.time.min_high, context->status_buffer.b.time.min_low,
+			context->status_buffer.b.time.sec_high, context->status_buffer.b.time.sec_low,
+			context->status_buffer.b.time.frame_high, context->status_buffer.b.time.frame_low,
+			context->status_buffer.b.time.flags, context->status_buffer.checksum
+		);
+	}
+}
+
+static void run_command(cdd_mcu *context)
+{
+	uint8_t check = checksum((uint8_t*)&context->cmd_buffer);
+	printf("cmd %X, checksum: %X, calc: %X\n", context->cmd_buffer.cmd_type, context->cmd_buffer.checksum, check);
+	if (check != context->cmd_buffer.checksum) {
+		context->error_status = DS_SUM_ERROR;
+		return;
+	}
+	if (context->cmd_buffer.must_be_zero) {
+		context->error_status = DS_CMD_ERROR;
+		return;
+	}
+	context->first_cmd_received = 1;
+	switch (context->cmd_buffer.cmd_type)
+	{
+	case CMD_NOP:
+		break;
+	case CMD_STOP:
+		puts("CDD CMD: STOP");
+		context->status = DS_STOP;
+		context->requested_format = SF_ABSOLUTE;
+		break;
+	case CMD_READ:
+	case CMD_SEEK: {
+		if (context->status == DS_DOOR_OPEN || context->status == DS_TRAY_MOVING || context->status == DS_DISC_LEADOUT || context->status == DS_DISC_LEADIN) {
+			context->error_status = DS_CMD_ERROR;
+			break;
+		}
+		if (context->requested_format == SF_TOCT || context->requested_format == SF_TOCN) {
+			context->requested_format = SF_ABSOLUTE;
+		}
+		if (!context->toc_valid) {
+			context->error_status = DS_CMD_ERROR;
+			break;
+		}
+		uint32_t lba = context->cmd_buffer.b.time.min_high * 10 + context->cmd_buffer.b.time.min_low;
+		lba *= 60;
+		lba += context->cmd_buffer.b.time.sec_high * 10 + context->cmd_buffer.b.time.sec_low;
+		lba *= 75;
+		lba += context->cmd_buffer.b.time.frame_high * 10 + context->cmd_buffer.b.time.frame_low;
+		printf("CDD CMD: %s cmd for lba %d, MM:SS:FF %u%u:%u%u:%u%u\n",
+			context->cmd_buffer.cmd_type == CMD_READ ? "READ" : "SEEK", lba,
+			context->cmd_buffer.b.time.min_high, context->cmd_buffer.b.time.min_low,
+			context->cmd_buffer.b.time.sec_high, context->cmd_buffer.b.time.sec_low,
+			context->cmd_buffer.b.time.frame_high, context->cmd_buffer.b.time.frame_low
+		);
+		if (lba >= context->media->tracks[0].fake_pregap + context->media->tracks[context->media->num_tracks - 1].end_lba) {
+			context->error_status = DS_CMD_ERROR;
+			break;
+		}
+		context->seek_pba = lba + LEADIN_SECTORS - 4;
+		context->seeking = 1;
+		context->status = context->cmd_buffer.cmd_type == CMD_READ ? DS_PLAY : DS_PAUSE;
+		break;
+	}
+	case CMD_REPORT_REQUEST:
+		switch (context->cmd_buffer.b.format.status_type)
+		{
+		case SF_ABSOLUTE:
+		case SF_RELATIVE:
+		case SF_TRACK:
+			context->requested_format = context->cmd_buffer.b.format.status_type;
+			break;
+		case SF_TOCO:
+			if (context->toc_valid) {
+				context->requested_format = SF_TOCO;
+			} else {
+				context->error_status = DS_CMD_ERROR;
+				context->requested_format = SF_ABSOLUTE;
+			}
+			break;
+		case SF_TOCT:
+			if (context->toc_valid) {
+				if (context->status == DS_STOP) {
+					context->status = DS_TOC_READ;
+					context->seeking = 1;
+					context->seek_pba = 0;
+				}
+			} else {
+				context->status = DS_TOC_READ;
+				context->seeking = 1;
+				context->seek_pba = 0;
+			}
+			context->requested_format = SF_TOCT;
+			break;
+		case SF_TOCN:
+			context->requested_track = context->cmd_buffer.b.format.track_high * 10;
+			context->requested_track += context->cmd_buffer.b.format.track_low;
+			if (!context->media || context->requested_track > context->media->num_tracks) {
+				context->requested_format = SF_ABSOLUTE;
+				context->error_status = DS_CMD_ERROR;
+			}
+			context->status = DS_TOC_READ;
+			context->seeking = 1;
+			context->seek_pba = 0;
+			context->requested_format = SF_TOCN;
+			break;
+		}
+		printf("CDD CMD: REPORT REQUEST(%d), format set to %d\n", context->cmd_buffer.b.format.status_type, context->requested_format);
+		break;
+	case CMD_PAUSE:
+		if (context->status == DS_DOOR_OPEN || context->status == DS_TRAY_MOVING || context->status == DS_DISC_LEADOUT || context->status == DS_DISC_LEADIN) {
+			context->error_status = DS_CMD_ERROR;
+			break;
+		}
+		if (context->requested_format == SF_TOCT || context->requested_format == SF_TOCN) {
+			context->requested_format = SF_ABSOLUTE;
+		}
+		if (!context->toc_valid) {
+			context->error_status = DS_CMD_ERROR;
+			break;
+		}
+		if (context->status == DS_STOP) {
+			context->seeking = 1;
+			context->seek_pba = LEADIN_SECTORS + context->media->tracks[0].fake_pregap + context->media->tracks[0].start_lba;
+			printf("CDD CMD: PAUSE, seeking to %u\n", context->seek_pba);
+		} else {
+			puts("CDD CMD: PAUSE");
+		}
+		context->status = DS_PAUSE;
+		break;
+	default:
+		printf("CDD CMD: Unimplemented(%d)\n", context->cmd_buffer.cmd_type);
+	}
+}
+
+#define BIT_HOCK 0x4
+#define BIT_DRS  0x2
+#define BIT_DTS  0x1
+
+void cdd_mcu_run(cdd_mcu *context, uint32_t cycle, uint16_t *gate_array, lc8951* cdc)
+{
+	uint32_t cd_cycle = mclks_to_cd_block(cycle);
+	if (!(gate_array[GAO_CDD_CTRL] & BIT_HOCK)) {
+		//it's a little unclear if this gates the actual cd block clock or just handshaking
+		//assum it's actually the clock for now
+		context->cycle = cd_cycle;
+		return;
+	}
+	uint32_t next_subcode = context->last_subcode_cycle + SECTOR_CLOCKS;
+	uint32_t next_nibble = context->current_status_nibble >= 0 ? context->last_nibble_cycle + NIBBLE_CLOCKS : CYCLE_NEVER;
+	uint32_t next_cmd_nibble = context->current_cmd_nibble >= 0 ? context->last_nibble_cycle + NIBBLE_CLOCKS : CYCLE_NEVER;
+	uint32_t next_byte = context->current_sector_byte >= 0 ? context->last_byte_cycle + BYTE_CLOCKS : CYCLE_NEVER;
+	for (; context->cycle < cd_cycle; context->cycle += CDD_MCU_DIVIDER)
+	{
+		if (context->cycle >= next_subcode) {
+			context->last_subcode_cycle = context->cycle;
+			next_subcode = context->cycle + SECTOR_CLOCKS;
+			update_status(context);
+			next_nibble = context->cycle;
+			context->current_status_nibble = 0;
+			gate_array[GAO_CDD_STATUS] |= BIT_DRS;
+			if (context->status == DS_PLAY && !context->seeking) {
+				next_byte = context->cycle;
+				context->current_sector_byte = 0;
+			}
+		}
+		if (context->cycle >= next_nibble) {
+			if (context->current_status_nibble == sizeof(cdd_status)) {
+				context->current_status_nibble = -1;
+				gate_array[GAO_CDD_STATUS] &= ~BIT_DRS;
+				if (context->cmd_recv_pending) {
+					context->cmd_recv_pending = 0;
+					context->current_cmd_nibble = 0;
+					gate_array[GAO_CDD_STATUS] |= BIT_DTS;
+					next_nibble = context->cycle + NIBBLE_CLOCKS;
+				} else {
+					context->cmd_recv_wait = 1;
+					next_nibble = CYCLE_NEVER;
+				}
+			} else {
+				uint8_t value = ((uint8_t *)&context->status_buffer)[context->current_status_nibble];
+				int ga_index = GAO_CDD_STATUS + (context->current_status_nibble >> 1);
+				if (context->current_status_nibble & 1) {
+					gate_array[ga_index] = value | (gate_array[ga_index]  & 0xFF00);
+				} else {
+					gate_array[ga_index] = (value << 8) | (gate_array[ga_index]  & 0x00FF);
+				}
+				if (context->current_status_nibble == 7) {
+					context->int_pending = 1;
+					context->next_int_cycle = cd_block_to_mclks(cycle + SECTOR_CLOCKS);
+				}
+				context->current_status_nibble++;
+				context->last_nibble_cycle = context->cycle;
+				next_nibble = context->cycle + NIBBLE_CLOCKS;
+			}
+		} else if (context->cycle >= next_cmd_nibble) {
+			if (context->current_cmd_nibble == sizeof(cdd_cmd)) {
+				next_cmd_nibble = CYCLE_NEVER;
+				context->current_cmd_nibble = -1;
+				gate_array[GAO_CDD_STATUS] &= ~BIT_DTS;
+				run_command(context);
+			} else {
+				int ga_index = GAO_CDD_CMD + (context->current_cmd_nibble >> 1);
+				uint8_t value = (context->current_cmd_nibble & 1) ? gate_array[ga_index] : gate_array[ga_index] >> 8;
+				((uint8_t *)&context->cmd_buffer)[context->current_cmd_nibble] = value;
+				context->current_cmd_nibble++;
+				context->last_nibble_cycle = context->cycle;
+				next_cmd_nibble = context->cycle + NIBBLE_CLOCKS;
+			}
+		}
+		if (context->cycle >= next_byte) {
+			uint8_t byte;
+			if (context->in_fake_pregap) {
+				if (!context->current_sector_byte || (context->current_sector_byte >= 16)) {
+					byte = 0;
+					//TODO: error detection and correction bytes
+				} else if (context->current_sector_byte < 12) {
+					byte = 0xFF;
+				} else if (context->current_sector_byte == 12) {
+					uint32_t minute = ((context->head_pba - LEADIN_SECTORS) / 75) / 60;
+					byte = (minute % 10) | ((minute / 10 ) << 4);
+				} else if (context->current_sector_byte == 13) {
+					uint32_t seconds = ((context->head_pba - LEADIN_SECTORS) / 75) % 60;
+					byte = (seconds % 10) | ((seconds / 10 ) << 4);
+				} else if (context->current_sector_byte == 14) {
+					uint32_t frames = (context->head_pba - LEADIN_SECTORS) % 75;
+					byte = (frames % 10) | ((frames / 10 ) << 4);
+				} else {
+					byte = 1;
+				}
+			} else {
+				byte = fgetc(context->media->f);
+			}
+			lc8951_write_byte(cdc, cd_block_to_mclks(context->cycle), context->current_sector_byte++, byte);
+			context->last_byte_cycle = context->cycle;
+			next_byte = context->cycle + BYTE_CLOCKS;
+			if (context->current_sector_byte == 2352) {
+				context->current_sector_byte = -1;
+
+			}
+		}
+	}
+}
+
+void cdd_mcu_start_cmd_recv(cdd_mcu *context, uint16_t *gate_array)
+{
+	if (context->cmd_recv_wait) {
+		context->current_cmd_nibble = 0;
+		gate_array[GAO_CDD_STATUS] |= BIT_DTS;
+		context->last_nibble_cycle = context->cycle;
+		context->cmd_recv_wait = 0;
+	} else {
+		context->cmd_recv_pending = 1;
+	}
+}
+
+void cdd_hock_enabled(cdd_mcu *context)
+{
+	context->last_subcode_cycle = context->cycle;
+	context->next_int_cycle = cd_block_to_mclks(context->cycle + SECTOR_CLOCKS + 7 * NIBBLE_CLOCKS);
+}
+
+void cdd_hock_disabled(cdd_mcu *context)
+{
+	context->last_subcode_cycle = CYCLE_NEVER;
+	context->next_int_cycle = CYCLE_NEVER;
+	context->last_nibble_cycle = CYCLE_NEVER;
+	context->current_status_nibble = -1;
+	context->current_cmd_nibble = -1;
+}
+
+void cdd_mcu_adjust_cycle(cdd_mcu *context, uint32_t deduction)
+{
+	uint32_t cd_deduction = mclks_to_cd_block(deduction);
+	if (context->next_int_cycle != CYCLE_NEVER) {
+		context->next_int_cycle -= deduction;
+	}
+	if (context->last_subcode_cycle != CYCLE_NEVER) {
+		context->last_subcode_cycle -= cd_deduction;
+	}
+	if (context->last_nibble_cycle != CYCLE_NEVER) {
+		context->last_nibble_cycle -= cd_deduction;
+	}
+	if (context->last_byte_cycle != CYCLE_NEVER) {
+		context->last_byte_cycle -= cd_deduction;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cdd_mcu.h	Sun Jan 30 22:29:29 2022 -0800
@@ -0,0 +1,168 @@
+#ifndef CDD_MCU_H_
+#define CDD_MCU_H_
+#include "system.h"
+#include "lc8951.h"
+
+typedef enum {
+	SF_ABSOLUTE,
+	SF_RELATIVE,
+	SF_TRACK,
+	SF_TOCO,
+	SF_TOCT,
+	SF_TOCN,
+	SF_E,
+	SF_NOTREADY = 0xF
+} status_format;
+
+typedef enum {
+	CMD_NOP,
+	CMD_STOP,
+	CMD_REPORT_REQUEST,
+	CMD_READ,
+	CMD_SEEK,
+	CMD_INVALID,
+	CMD_PAUSE,
+	CMD_PLAY,
+	CMD_FFWD,
+	CMD_RWD,
+	CMD_TRACK_SKIP,
+	CMD_TRACK_CUE,
+	CMD_DOOR_CLOSE,
+	CMD_DOOR_OPEN
+} host_cmd;
+
+typedef enum {
+	DS_STOP,
+	DS_PLAY,
+	DS_SEEK,
+	DS_SCAN,
+	DS_PAUSE,
+	DS_DOOR_OPEN,
+	DS_SUM_ERROR,
+	DS_CMD_ERROR,
+	DS_FUNC_ERROR,
+	DS_TOC_READ,
+	DS_TRACKING,
+	DS_NO_DISC,
+	DS_DISC_LEADOUT,
+	DS_DISC_LEADIN,
+	DS_TRAY_MOVING,
+} drive_status;
+
+
+typedef struct {
+	uint8_t status;
+	uint8_t format;
+	union {
+		struct {
+			uint8_t min_high;
+			uint8_t min_low;
+			uint8_t sec_high;
+			uint8_t sec_low;
+			uint8_t frame_high;
+			uint8_t frame_low;
+			uint8_t flags;
+		} time;
+		struct {
+			uint8_t track_high;
+			uint8_t track_low;
+			uint8_t padding0;
+			uint8_t padding1;
+			uint8_t control;
+			uint8_t adr;
+			uint8_t flags;
+		} track;
+		struct {
+			uint8_t first_track_high;
+			uint8_t first_track_low;
+			uint8_t last_track_high;
+			uint8_t last_track_low;
+			uint8_t version;
+			uint8_t padding;
+			uint8_t flags;
+		} toct;
+		struct {
+			uint8_t min_high;
+			uint8_t min_low;
+			uint8_t sec_high;
+			uint8_t sec_low;
+			uint8_t frame_high;
+			uint8_t frame_low;
+			uint8_t track_low;
+		} tocn;
+		struct {
+		} error;
+	} b;
+	uint8_t checksum;
+} cdd_status;
+
+typedef struct {
+	uint8_t cmd_type;
+	uint8_t must_be_zero;
+	union {
+		struct {
+			uint8_t min_high;
+			uint8_t min_low;
+			uint8_t sec_high;
+			uint8_t sec_low;
+			uint8_t frame_high;
+			uint8_t frame_low;
+		} time;
+		struct {
+			uint8_t padding;
+			uint8_t status_type;
+			uint8_t track_high;
+			uint8_t track_low;
+		} format;
+		struct {
+			uint8_t padding;
+			uint8_t direction;
+			uint8_t tracks_highest;
+			uint8_t tracks_midhigh;
+			uint8_t tracks_midlow;
+			uint8_t tracks_lowest;
+		} skip;
+		struct {
+			uint8_t track_high;
+			uint8_t track_low;
+		} track;
+	} b;
+	uint8_t padding;
+	uint8_t checksum;
+} cdd_cmd;
+
+typedef struct {
+	system_media  *media;
+	uint32_t      cycle;          //this is in CD block CLKS
+	uint32_t      next_int_cycle; //this is in SCD MCLKS
+	uint32_t      last_subcode_cycle;
+	uint32_t      last_nibble_cycle;
+	uint32_t      last_byte_cycle;
+	int           current_status_nibble;
+	int           current_cmd_nibble;
+	int           current_sector_byte;
+	uint32_t      head_pba;
+	uint32_t      seek_pba;
+	cdd_status    status_buffer;
+	cdd_cmd       cmd_buffer;
+	status_format requested_format;
+	drive_status  status;
+	drive_status  error_status;
+	uint8_t       requested_track;
+	uint8_t       cmd_recv_wait;
+	uint8_t       cmd_recv_pending;
+	uint8_t       int_pending;
+	uint8_t       toc_valid;
+	uint8_t       first_cmd_received;
+	uint8_t       seeking;
+	uint8_t       in_fake_pregap;
+} cdd_mcu;
+
+void cdd_mcu_init(cdd_mcu *context, system_media *media);
+void cdd_mcu_run(cdd_mcu *context, uint32_t cycle, uint16_t *gate_array, lc8951* cdc);
+void cdd_hock_enabled(cdd_mcu *context);
+void cdd_hock_disabled(cdd_mcu *context);
+void cdd_mcu_start_cmd_recv(cdd_mcu *context, uint16_t *gate_array);
+void cdd_mcu_adjust_cycle(cdd_mcu *context, uint32_t deduction);
+
+#endif //CD_MCU_H_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cue.c	Sun Jan 30 22:29:29 2022 -0800
@@ -0,0 +1,159 @@
+#include <ctype.h>
+
+#include "system.h"
+#include "util.h"
+
+static char* cmd_start(char *cur)
+{
+	while (*cur && isblank(*cur))
+	{
+		cur++;
+	}
+	return cur;
+}
+
+static char* next_line(char *cur)
+{
+	while (*cur && *cur != '\n')
+	{
+		cur++;
+	}
+	if (*cur) {
+		return cur + 1;
+	}
+	return NULL;
+}
+
+static uint32_t timecode_to_lba(char *timecode)
+{
+	char *end;
+	int seconds = 0, frames = 0;
+	int minutes = strtol(timecode, &end, 10);
+	if (end) {
+		timecode = end + 1;
+		seconds = strtol(timecode, &end, 10);
+		if (end) {
+			timecode = end + 1;
+			frames = strtol(timecode, NULL, 10);
+		}
+	}
+	seconds += minutes * 60;
+	return seconds * 75 + frames;
+
+}
+
+uint8_t parse_cue(system_media *media)
+{
+	char *line = media->buffer;
+	media->num_tracks = 0;
+	do {
+		char *cmd = cmd_start(line);
+		if (cmd) {
+			if (startswith(cmd, "TRACK ")) {
+				media->num_tracks++;
+			}
+			line = next_line(cmd);
+		} else {
+			line = NULL;
+		}
+	} while (line);
+	track_info *tracks = calloc(sizeof(track_info), media->num_tracks);
+	media->tracks = tracks;
+	line = media->buffer;
+	int track = -1;
+	do {
+		char *cmd = cmd_start(line);
+		if (cmd) {
+			if (startswith(cmd, "TRACK ")) {
+				track++;
+				cmd += 6;
+				char *end;
+				int file_track = strtol(cmd, &end, 10);
+				if (file_track != (track + 1)) {
+					warning("Expected track %d, but found track %d in CUE sheet\n", track + 1, file_track);
+				}
+				cmd = cmd_start(end);
+				if (cmd) {
+					tracks[track].type = startswith(cmd, "AUDIO") ? TRACK_AUDIO : TRACK_DATA;
+				}
+			} else if (startswith(cmd, "FILE ")) {
+				if (media->f) {
+					warning("CUE sheets with multiple FILE commands are not supported\n");
+				} else {
+					cmd += 5;
+					cmd = strchr(cmd, '"');
+					if (cmd) {
+						cmd++;
+						char *end = strchr(cmd, '"');
+						if (end) {
+							char *fname;
+							//TODO: zipped BIN/CUE support
+							if (is_absolute_path(cmd)) {
+								fname = malloc(end-cmd + 1);
+								memcpy(fname, cmd, end-cmd);
+								fname[end-cmd] = 0;
+							} else {
+								size_t dirlen = strlen(media->dir);
+								fname = malloc(dirlen + 1 + (end-cmd) + 1);
+								memcpy(fname, media->dir, dirlen);
+								fname[dirlen] = PATH_SEP[0];
+								memcpy(fname + dirlen + 1, cmd, end-cmd);
+								fname[dirlen + 1 + (end-cmd)] = 0;
+							}
+							media->f = fopen(fname, "rb");
+							if (!media->f) {
+								fatal_error("Failed to open %s specified by FILE command in CUE sheet %s.%s\n", fname, media->name, media->extension);
+							}
+							free(fname);
+						}
+					}
+				}
+			} else if (track >= 0) {
+				if (startswith(cmd, "PREGAP ")) {
+					tracks[track].fake_pregap = timecode_to_lba(cmd + 7);
+				} else if (startswith(cmd, "INDEX ")) {
+					char *after;
+					int index = strtol(cmd + 6, &after, 10);
+					if (!index) {
+						tracks[track].pregap_lba = timecode_to_lba(after);
+					} else if (index == 1) {
+						tracks[track].start_lba = timecode_to_lba(after);
+					}
+				}
+			}
+			if (cmd) {
+				line = next_line(cmd);
+			} else {
+				line = NULL;
+			}
+		} else {
+			line = NULL;
+		}
+	} while (line);
+	for (uint32_t i = 0; i < (media->num_tracks - 1); i++)
+	{
+		uint32_t next = i + 1;
+		tracks[i].end_lba = tracks[next].pregap_lba ? tracks[next].pregap_lba : tracks[next].start_lba;
+	}
+	if (media->f) {
+		//end of last track is implicitly defined by file size
+		tracks[media->num_tracks-1].end_lba = file_size(media->f) / 2352;
+		//replace cue sheet with first sector
+		free(media->buffer);
+		media->buffer = calloc(2048, 1);
+		if (tracks[0].type = TRACK_DATA) {
+			// if the first track is a data track, don't trust the CUE sheet and look at the MM:SS:FF from first sector
+			uint8_t msf[3];
+			fseek(media->f, 12, SEEK_SET);
+			if (sizeof(msf) == fread(msf, 1, sizeof(msf), media->f)) {
+				tracks[0].fake_pregap = msf[2] + (msf[0] * 60 + msf[1]) * 75;
+			}
+		}
+
+		fseek(media->f, 16, SEEK_SET);
+		media->size = fread(media->buffer, 1, 2048, media->f);
+	}
+	uint8_t valid = tracks > 0 && media->f != NULL;
+	media->type = valid ? MEDIA_CDROM : MEDIA_CART;
+	return valid;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cue.h	Sun Jan 30 22:29:29 2022 -0800
@@ -0,0 +1,6 @@
+#ifndef CUE_H_
+#define CUE_H_
+
+uint8_t parse_cue(system_media *media);
+
+#endif //CUE_H_
--- a/debug.c	Sat Jan 01 18:54:46 2022 -0800
+++ b/debug.c	Sun Jan 30 22:29:29 2022 -0800
@@ -740,7 +740,7 @@
 				m68k_disasm(&inst, input_buf);
 				printf("%X: %s\n", address, input_buf);
 			}
-			
+
 			break;
 		case 'n':
 			if (inst.op == M68K_RTS) {
@@ -1012,7 +1012,7 @@
 
 	init_terminal();
 
-	sync_components(context, 0);
+	context->options->sync_components(context, 0);
 	genesis_context *gen = context->system;
 	vdp_force_update_framebuffer(gen->vdp);
 	//probably not necessary, but let's play it safe
--- a/genesis.c	Sat Jan 01 18:54:46 2022 -0800
+++ b/genesis.c	Sun Jan 30 22:29:29 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>
@@ -246,13 +247,22 @@
 uint16_t read_dma_value(uint32_t address)
 {
 	genesis_context *genesis = (genesis_context *)current_system;
+	address *= 2;
 	//TODO: Figure out what happens when you try to DMA from weird adresses like IO or banked Z80 area
 	if ((address >= 0xA00000 && address < 0xB00000) || (address >= 0xC00000 && address <= 0xE00000)) {
 		return 0;
 	}
+	if (genesis->expansion) {
+		segacd_context *cd = genesis->expansion;
+		uint32_t word_ram = cd->base + 0x200000;
+		uint32_t word_ram_end = cd->base + 0x240000;
+		if (address >= word_ram && address < word_ram_end) {
+			//FIXME: first word should just be garbage
+			address -= 2;
+		}
+	}
 
-	//addresses here are word addresses (i.e. bit 0 corresponds to A1), so no need to do multiply by 2
-	return read_word(address * 2, (void **)genesis->m68k->mem_pointers, &genesis->m68k->options->gen, genesis->m68k);
+	return read_word(address, (void **)genesis->m68k->mem_pointers, &genesis->m68k->options->gen, genesis->m68k);
 }
 
 static uint16_t get_open_bus_value(system_header *system)
@@ -412,7 +422,7 @@
 #define ADJUST_BUFFER (8*MCLKS_LINE*313)
 #define MAX_NO_ADJUST (UINT_MAX-ADJUST_BUFFER)
 
-m68k_context * sync_components(m68k_context * context, uint32_t address)
+static m68k_context *sync_components(m68k_context * context, uint32_t address)
 {
 	genesis_context * gen = context->system;
 	vdp_context * v_context = gen->vdp;
@@ -433,6 +443,9 @@
 	io_run(gen->io.ports, mclks);
 	io_run(gen->io.ports + 1, mclks);
 	io_run(gen->io.ports + 2, mclks);
+	if (gen->expansion) {
+		scd_run(gen->expansion, gen_cycle_to_scd(mclks, gen));
+	}
 	if (mclks >= gen->reset_cycle) {
 		gen->reset_requested = 1;
 		context->should_return = 1;
@@ -470,6 +483,9 @@
 				gen->reset_cycle -= deduction;
 			}
 			event_cycle_adjust(mclks, deduction);
+			if (gen->expansion) {
+				scd_adjust_cycle(gen->expansion, deduction);
+			}
 			gen->last_flush_cycle -= deduction;
 		}
 	} else if (mclks - gen->last_flush_cycle > gen->soft_flush_cycles) {
@@ -1410,7 +1426,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 +1764,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 +1773,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 +1858,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,45 +1889,92 @@
 			gen->vdp->vsram[i] = rand();
 		}
 	}
-	setup_io_devices(config, rom, &gen->io);
-	gen->header.has_keyboard = io_has_keyboard(&gen->io);
+
+	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},
+	{0x000000, 0xFFFFFF, 0xFFFFFF, 0, 0, 0,                                   NULL,
+			   (read_16_fun)unused_read,    (write_16_fun)unused_write,
+			   (read_8_fun)unused_read_b,   (write_8_fun)unused_write_b}
+};
+const size_t base_chunks = sizeof(base_map)/sizeof(*base_map);
 
-	gen->mapper_type = rom->mapper_type;
-	gen->save_type = rom->save_type;
+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
+	byteswap_rom(rom_size, rom);
+	if (lock_on) {
+		byteswap_rom(lock_on_size, lock_on);
+	}
+#endif
+	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 = shared_init(ym_opts, &info, force_region);
+	gen->z80->mem_pointers[1] = gen->z80->mem_pointers[2] = rom;
+
+	gen->cart = rom;
+	gen->lock_on = lock_on;
+
+	setup_io_devices(config, &info, &gen->io);
+	gen->header.has_keyboard = io_has_keyboard(&gen->io);
+	gen->mapper_type = info.mapper_type;
+	gen->save_type = info.save_type;
 	if (gen->save_type != SAVE_NONE) {
-		gen->save_ram_mask = rom->save_mask;
-		gen->save_size = rom->save_size;
-		gen->save_storage = rom->save_buffer;
-		gen->eeprom_map = rom->eeprom_map;
-		gen->num_eeprom = rom->num_eeprom;
+		gen->save_ram_mask = info.save_mask;
+		gen->save_size = info.save_size;
+		gen->save_storage = info.save_buffer;
+		gen->eeprom_map = info.eeprom_map;
+		gen->num_eeprom = info.num_eeprom;
 		if (gen->save_type == SAVE_I2C) {
 			eeprom_init(&gen->eeprom, gen->save_storage, gen->save_size);
 		} else if (gen->save_type == SAVE_NOR) {
-			memcpy(&gen->nor, rom->nor, sizeof(gen->nor));
-			//nor_flash_init(&gen->nor, gen->save_storage, gen->save_size, rom->save_page_size, rom->save_product_id, rom->save_bus);
+			memcpy(&gen->nor, info.nor, sizeof(gen->nor));
+			//nor_flash_init(&gen->nor, gen->save_storage, gen->save_size, info.save_page_size, info.save_product_id, info.save_bus);
 		}
 	} else {
 		gen->save_storage = NULL;
 	}
 
-	gen->mapper_start_index = rom->mapper_start_index;
+	gen->mapper_start_index = info.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;
-	for (int i = 0; i < rom->map_chunks; i++)
+	for (int i = 0; i < info.map_chunks; i++)
 	{
-		if (rom->map[i].start == 0xE00000) {
-			rom->map[i].buffer = gen->work_ram;
+		if (info.map[i].start == 0xE00000) {
+			info.map[i].buffer = gen->work_ram;
 			if (!tmss) {
 				break;
 			}
 		}
-		if (rom->map[i].flags & MMAP_PTR_IDX && rom->map[i].ptr_index >= next_ptr_index) {
-			next_ptr_index = rom->map[i].ptr_index + 1;
+		if (info.map[i].flags & MMAP_PTR_IDX && info.map[i].ptr_index >= next_ptr_index) {
+			next_ptr_index = info.map[i].ptr_index + 1;
 		}
-		if (rom->map[i].start < 0x400000 && rom->map[i].read_16 != unused_read) {
-			uint32_t highest_offset = (rom->map[i].end & rom->map[i].mask) + 1;
+		if (info.map[i].start < 0x400000 && info.map[i].read_16 != unused_read) {
+			uint32_t highest_offset = (info.map[i].end & info.map[i].mask) + 1;
 			if (highest_offset > tmss_min_alloc) {
 				tmss_min_alloc = highest_offset;
 			}
@@ -1941,105 +2013,136 @@
 		}
 		//modify mappings for ROM space to point to the TMSS ROM and fixup flags to allow switching back and forth
 		//WARNING: This code makes some pretty big assumptions about the kinds of map chunks it will encounter
-		for (int i = 0; i < rom->map_chunks; i++)
+		for (int i = 0; i < info.map_chunks; i++)
 		{
-			if (rom->map[i].start < 0x400000 && rom->map[i].read_16 != unused_read) {
-				if (rom->map[i].flags == MMAP_READ) {
+			if (info.map[i].start < 0x400000 && info.map[i].read_16 != unused_read) {
+				if (info.map[i].flags == MMAP_READ) {
 					//Normal ROM
-					rom->map[i].flags |= MMAP_PTR_IDX | MMAP_CODE;
-					rom->map[i].ptr_index = next_ptr_index++;
-					if (rom->map[i].ptr_index >= NUM_MEM_AREAS) {
+					info.map[i].flags |= MMAP_PTR_IDX | MMAP_CODE;
+					info.map[i].ptr_index = next_ptr_index++;
+					if (info.map[i].ptr_index >= NUM_MEM_AREAS) {
 						fatal_error("Too many memmap chunks with MMAP_PTR_IDX after TMSS remap\n");
 					}
-					gen->tmss_pointers[rom->map[i].ptr_index] = rom->map[i].buffer;
-					rom->map[i].buffer = buffer + (rom->map[i].start & ~rom->map[i].mask & (tmss_size - 1));
-				} else if (rom->map[i].flags & MMAP_PTR_IDX) {
+					gen->tmss_pointers[info.map[i].ptr_index] = info.map[i].buffer;
+					info.map[i].buffer = buffer + (info.map[i].start & ~info.map[i].mask & (tmss_size - 1));
+				} else if (info.map[i].flags & MMAP_PTR_IDX) {
 					//Sega mapper page or multi-game mapper
-					gen->tmss_pointers[rom->map[i].ptr_index] = rom->map[i].buffer;
-					rom->map[i].buffer = buffer + (rom->map[i].start & ~rom->map[i].mask & (tmss_size - 1));
-					if (rom->map[i].write_16) {
+					gen->tmss_pointers[info.map[i].ptr_index] = info.map[i].buffer;
+					info.map[i].buffer = buffer + (info.map[i].start & ~info.map[i].mask & (tmss_size - 1));
+					if (info.map[i].write_16) {
 						if (!gen->tmss_write_16) {
-							gen->tmss_write_16 = rom->map[i].write_16;
-							gen->tmss_write_8 = rom->map[i].write_8;
-							rom->map[i].write_16 = tmss_rom_write_16;
-							rom->map[i].write_8 = tmss_rom_write_8;
-						} else if (gen->tmss_write_16 == rom->map[i].write_16) {
-							rom->map[i].write_16 = tmss_rom_write_16;
-							rom->map[i].write_8 = tmss_rom_write_8;
+							gen->tmss_write_16 = info.map[i].write_16;
+							gen->tmss_write_8 = info.map[i].write_8;
+							info.map[i].write_16 = tmss_rom_write_16;
+							info.map[i].write_8 = tmss_rom_write_8;
+						} else if (gen->tmss_write_16 == info.map[i].write_16) {
+							info.map[i].write_16 = tmss_rom_write_16;
+							info.map[i].write_8 = tmss_rom_write_8;
 						} else {
-							warning("Chunk starting at %X has a write function, but we've already stored a different one for TMSS remap\n", rom->map[i].start);
+							warning("Chunk starting at %X has a write function, but we've already stored a different one for TMSS remap\n", info.map[i].start);
 						}
 					}
-				} else if ((rom->map[i].flags & (MMAP_READ | MMAP_WRITE)) == (MMAP_READ | MMAP_WRITE)) {
+				} else if ((info.map[i].flags & (MMAP_READ | MMAP_WRITE)) == (MMAP_READ | MMAP_WRITE)) {
 					//RAM or SRAM
-					rom->map[i].flags |= MMAP_PTR_IDX;
-					rom->map[i].ptr_index = next_ptr_index++;
-					gen->tmss_pointers[rom->map[i].ptr_index] = rom->map[i].buffer;
-					rom->map[i].buffer = buffer + (rom->map[i].start & ~rom->map[i].mask & (tmss_size - 1));
-					if (!gen->tmss_write_offset || gen->tmss_write_offset == rom->map[i].start) {
-						gen->tmss_write_offset = rom->map[i].start;
-						rom->map[i].flags &= ~MMAP_WRITE;
-						if (rom->map[i].flags & MMAP_ONLY_ODD) {
-							rom->map[i].write_16 = tmss_odd_write_16;
-							rom->map[i].write_8 = tmss_odd_write_8;
-						} else if (rom->map[i].flags & MMAP_ONLY_EVEN) {
-							rom->map[i].write_16 = tmss_even_write_16;
-							rom->map[i].write_8 = tmss_even_write_8;
+					info.map[i].flags |= MMAP_PTR_IDX;
+					info.map[i].ptr_index = next_ptr_index++;
+					gen->tmss_pointers[info.map[i].ptr_index] = info.map[i].buffer;
+					info.map[i].buffer = buffer + (info.map[i].start & ~info.map[i].mask & (tmss_size - 1));
+					if (!gen->tmss_write_offset || gen->tmss_write_offset == info.map[i].start) {
+						gen->tmss_write_offset = info.map[i].start;
+						info.map[i].flags &= ~MMAP_WRITE;
+						if (info.map[i].flags & MMAP_ONLY_ODD) {
+							info.map[i].write_16 = tmss_odd_write_16;
+							info.map[i].write_8 = tmss_odd_write_8;
+						} else if (info.map[i].flags & MMAP_ONLY_EVEN) {
+							info.map[i].write_16 = tmss_even_write_16;
+							info.map[i].write_8 = tmss_even_write_8;
 						} else {
-							rom->map[i].write_16 = tmss_word_write_16;
-							rom->map[i].write_8 = tmss_word_write_8;
+							info.map[i].write_16 = tmss_word_write_16;
+							info.map[i].write_8 = tmss_word_write_8;
 						}
 					} else {
-						warning("Could not remap writes for chunk starting at %X for TMSS because write_offset is %X\n", rom->map[i].start, gen->tmss_write_offset);
+						warning("Could not remap writes for chunk starting at %X for TMSS because write_offset is %X\n", info.map[i].start, gen->tmss_write_offset);
 					}
-				} else if (rom->map[i].flags & MMAP_READ_CODE) {
+				} else if (info.map[i].flags & MMAP_READ_CODE) {
 					//NOR flash
-					rom->map[i].flags |= MMAP_PTR_IDX;
-					rom->map[i].ptr_index = next_ptr_index++;
-					if (rom->map[i].ptr_index >= NUM_MEM_AREAS) {
+					info.map[i].flags |= MMAP_PTR_IDX;
+					info.map[i].ptr_index = next_ptr_index++;
+					if (info.map[i].ptr_index >= NUM_MEM_AREAS) {
 						fatal_error("Too many memmap chunks with MMAP_PTR_IDX after TMSS remap\n");
 					}
-					gen->tmss_pointers[rom->map[i].ptr_index] = rom->map[i].buffer;
-					rom->map[i].buffer = buffer + (rom->map[i].start & ~rom->map[i].mask & (tmss_size - 1));
+					gen->tmss_pointers[info.map[i].ptr_index] = info.map[i].buffer;
+					info.map[i].buffer = buffer + (info.map[i].start & ~info.map[i].mask & (tmss_size - 1));
 					if (!gen->tmss_write_16) {
-						gen->tmss_write_16 = rom->map[i].write_16;
-						gen->tmss_write_8 = rom->map[i].write_8;
-						gen->tmss_read_16 = rom->map[i].read_16;
-						gen->tmss_read_8 = rom->map[i].read_8;
-						rom->map[i].write_16 = tmss_rom_write_16;
-						rom->map[i].write_8 = tmss_rom_write_8;
-						rom->map[i].read_16 = tmss_rom_read_16;
-						rom->map[i].read_8 = tmss_rom_read_8;
-					} else if (gen->tmss_write_16 == rom->map[i].write_16) {
-						rom->map[i].write_16 = tmss_rom_write_16;
-						rom->map[i].write_8 = tmss_rom_write_8;
-						rom->map[i].read_16 = tmss_rom_read_16;
-						rom->map[i].read_8 = tmss_rom_read_8;
+						gen->tmss_write_16 = info.map[i].write_16;
+						gen->tmss_write_8 = info.map[i].write_8;
+						gen->tmss_read_16 = info.map[i].read_16;
+						gen->tmss_read_8 = info.map[i].read_8;
+						info.map[i].write_16 = tmss_rom_write_16;
+						info.map[i].write_8 = tmss_rom_write_8;
+						info.map[i].read_16 = tmss_rom_read_16;
+						info.map[i].read_8 = tmss_rom_read_8;
+					} else if (gen->tmss_write_16 == info.map[i].write_16) {
+						info.map[i].write_16 = tmss_rom_write_16;
+						info.map[i].write_8 = tmss_rom_write_8;
+						info.map[i].read_16 = tmss_rom_read_16;
+						info.map[i].read_8 = tmss_rom_read_8;
 					} else {
-						warning("Chunk starting at %X has a write function, but we've already stored a different one for TMSS remap\n", rom->map[i].start);
+						warning("Chunk starting at %X has a write function, but we've already stored a different one for TMSS remap\n", info.map[i].start);
 					}
 				} else {
-					warning("Didn't remap chunk starting at %X for TMSS because it has flags %X\n", rom->map[i].start, rom->map[i].flags);
+					warning("Didn't remap chunk starting at %X for TMSS because it has flags %X\n", info.map[i].start, info.map[i].flags);
 				}
 			}
 		}
 		gen->tmss_buffer = buffer;
 	}
+	memmap_chunk* map = info.map;
+	uint32_t map_chunks = info.map_chunks;
+	if (info.wants_cd) {
+		segacd_context *cd = alloc_configure_segacd((system_media *)current_media(), 0, force_region, &info);
+		gen->expansion = cd;
+		gen->version_reg &= ~NO_DISK;
+		cd->genesis = gen;
+		uint32_t cd_chunks;
+		memmap_chunk *cd_map = segacd_main_cpu_map(gen->expansion, 1, &cd_chunks);
+		map_chunks = cd_chunks + info.map_chunks;
+		map = calloc(map_chunks, sizeof(memmap_chunk));
+		memcpy(map, info.map, sizeof(memmap_chunk) * (info.map_chunks - 1));
+		memcpy(map + info.map_chunks - 1, cd_map, sizeof(memmap_chunk) * cd_chunks);
+		memcpy(map + map_chunks - 1, info.map + info.map_chunks - 1, sizeof(memmap_chunk));
+		free(info.map);
+		int max_ptr_index = -1;
+		for (int i = 0; i < info.map_chunks - 1; i++)
+		{
+			if (map[i].flags & MMAP_PTR_IDX && map[i].ptr_index > max_ptr_index) {
+				max_ptr_index = map[i].ptr_index;
+			}
+		}
+		cd->memptr_start_index + max_ptr_index + 1;
+		for (int i = info.map_chunks - 1; i < map_chunks - 1; i++)
+		{
+			if (map[i].flags & MMAP_PTR_IDX) {
+				map[i].ptr_index += cd->memptr_start_index;
+			}
+		}
+		cd->base = 0x400000;
+	}
 
 	m68k_options *opts = malloc(sizeof(m68k_options));
-	init_m68k_opts(opts, rom->map, rom->map_chunks, MCLKS_PER_68K);
+	init_m68k_opts(opts, map, map_chunks, MCLKS_PER_68K, sync_components);
 	if (!strcmp(tern_find_ptr_default(model, "tas", "broken"), "broken")) {
 		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;
+	opts->address_log = (ym_opts & OPT_ADDRESS_LOG) ? fopen("address.log", "w") : NULL;
 
 	//This must happen after the 68K context has been allocated
-	for (int i = 0; i < rom->map_chunks; i++)
+	for (int i = 0; i < map_chunks; i++)
 	{
-		if (rom->map[i].flags & MMAP_PTR_IDX) {
-			gen->m68k->mem_pointers[rom->map[i].ptr_index] = rom->map[i].buffer;
+		if (map[i].flags & MMAP_PTR_IDX) {
+			gen->m68k->mem_pointers[map[i].ptr_index] = map[i].buffer;
 		}
 	}
 
@@ -2055,41 +2158,45 @@
 	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)
+genesis_context *alloc_config_genesis_cdboot(system_media *media, uint32_t system_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},
-		{0x000000, 0xFFFFFF, 0xFFFFFF, 0, 0, 0,                                   NULL,
-		           (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();
+	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;
+	gen->version_reg &= ~NO_DISK;
+	cd->genesis = gen;
+	setup_io_devices(config, &info, &gen->io);
+
+	uint32_t cd_chunks;
+	memmap_chunk *cd_map = segacd_main_cpu_map(gen->expansion, 0, &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, sync_components);
+	//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;
+		}
 	}
-	rom_info info = configure_rom(rom_db, rom, rom_size, lock_on, lock_on_size, base_map, sizeof(base_map)/sizeof(base_map[0]));
-	rom = info.rom;
-	rom_size = info.rom_size;
-#ifndef BLASTEM_BIG_ENDIAN
-	byteswap_rom(rom_size, rom);
-	if (lock_on) {
-		byteswap_rom(lock_on_size, lock_on);
-	}
-#endif
-	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;
-	}
-	return alloc_init_genesis(&info, rom, lock_on, ym_opts, force_region);
+	return gen;
 }
--- a/genesis.h	Sat Jan 01 18:54:46 2022 -0800
+++ b/genesis.h	Sun Jan 30 22:29:29 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;
@@ -80,8 +81,8 @@
 #define RAM_WORDS 32 * 1024
 #define Z80_RAM_BYTES 8 * 1024
 
-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);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lc8951.c	Sun Jan 30 22:29:29 2022 -0800
@@ -0,0 +1,326 @@
+#include "lc8951.h"
+#include "backend.h"
+
+enum {
+	COMIN,
+	IFSTAT,
+	DBCL,
+	DBCH,
+	HEAD0,
+	HEAD1,
+	HEAD2,
+	HEAD3,
+	PTL,
+	PTH,
+	WAL,
+	WAH,
+	STAT0,
+	STAT1,
+	STAT2,
+	STAT3,
+
+	SBOUT = COMIN,
+	IFCTRL = IFSTAT,
+	DACL = HEAD0,
+	DACH = HEAD1,
+	DTTRG = HEAD2,
+	DTACK = HEAD3,
+	WAL_WRITE = PTL,
+	WAH_WRITE = PTH,
+	CTRL0 = WAL,
+	CTRL1 = WAH,
+	PTL_WRITE = STAT0,
+	PTH_WRITE = STAT1,
+	RESET = STAT3
+};
+
+//IFCTRL
+#define BIT_CMDIEN 0x80
+#define BIT_DTEIEN 0x40
+#define BIT_DECIEN 0x20
+#define BIT_CMDBK  0x10
+#define BIT_DTWAI  0x08
+#define BIT_STWAI  0x04
+#define BIT_DOUTEN 0x02
+#define BIT_SOUTEN 0x01
+
+//IFSTAT
+#define BIT_CMDI   0x80
+#define BIT_DTEI   0x40
+#define BIT_DECI   0x20
+#define BIT_DTBSY  0x08
+#define BIT_STBSY  0x04
+#define BIT_DTEN   0x02
+#define BIT_STEN   0x01
+
+//CTRL0
+#define BIT_DECEN 0x80
+#define BIT_WRRQ  0x04
+
+//STAT0
+#define BIT_CRCOK 0x80
+
+//STAT3
+#define BIT_VALST 0x80
+
+//datasheet timing info
+//3 cycles for memory operation
+//6 cycles min for DMA-mode host transfer
+
+void lc8951_init(lc8951 *context, lcd8951_byte_recv_fun byte_handler, void *handler_data)
+{
+	//This seems to vary somewhat between Sega CD models
+	//unclear if the difference is in the lc8951 or gate array
+	context->regs[IFSTAT] = 0xFF;
+	context->ar_mask = 0x1F;
+	context->clock_step = (2 + 2) * 6; // external divider, internal divider + DMA period
+	context->byte_handler = byte_handler;
+	context->handler_data = handler_data;
+	context->decode_end = CYCLE_NEVER;
+	context->transfer_end = CYCLE_NEVER;
+}
+
+void lc8951_reg_write(lc8951 *context, uint8_t value)
+{
+	switch (context->ar)
+	{
+	case SBOUT:
+		context->regs[context->ar] = value;
+		if (context->ifctrl & BIT_SOUTEN) {
+			context->regs[IFSTAT] &= ~BIT_STBSY;
+		}
+		break;
+	case IFCTRL:
+		context->ifctrl = value;
+		if (!(value & BIT_SOUTEN)) {
+			context->regs[IFSTAT] |= BIT_STBSY;
+		}
+		if (!(value & BIT_DOUTEN)) {
+			context->regs[IFSTAT] |= BIT_DTBSY|BIT_DTEI;
+			context->transfer_end = CYCLE_NEVER;
+		}
+		break;
+	case DBCL:
+		context->regs[context->ar] = value;
+		break;
+	case DBCH:
+		context->regs[context->ar] = value & 0xF;
+		break;
+	case DACL:
+		context->dac &= 0xFF00;
+		context->dac |= value;
+		break;
+	case DACH:
+		context->dac &= 0xFF;
+		context->dac |= value << 8;
+		break;
+	case DTTRG:
+		if (context->ifctrl & BIT_DOUTEN) {
+			context->regs[IFSTAT] &= ~BIT_DTBSY;
+			uint16_t transfer_size = context->regs[DBCL] | (context->regs[DBCH] << 8);
+			context->transfer_end = context->cycle + transfer_size * context->clock_step;
+			printf("DTTRG: size %u, cycle %u, end %u\n", transfer_size, context->cycle, context->transfer_end);
+		}
+		break;
+	case DTACK:
+		context->regs[IFSTAT] |= BIT_DTEI;
+		break;
+	case WAL_WRITE:
+		context->regs[WAL] = value;
+		break;
+	case WAH_WRITE:
+		context->regs[WAH] = value;
+		break;
+	case CTRL0:
+		context->ctrl0 = value;
+		break;
+	case CTRL1:
+		context->ctrl1 = value;
+		break;
+	case PTL_WRITE:
+		context->regs[PTL] = value;
+		break;
+	case PTH_WRITE:
+		context->regs[PTH] = value;
+		//TODO: Datasheet says any write to PT triggers a decode, but initial tests suggest that's not the case
+		//Need to do more tests with other CTRL0/CTRL1 settings
+		//context->decode_end = context->cycle + 2352 * context->clock_step * 4;
+		break;
+	case RESET:
+		context->comin_count = 0;
+		context->regs[IFSTAT] = 0xFF;
+		break;
+	default:
+		break;
+	}
+	if (context->ar != SBOUT) {
+		context->ar++;
+		context->ar &= context->ar_mask;
+	}
+}
+
+uint8_t lc8951_reg_read(lc8951 *context)
+{
+	uint8_t value;
+	if (context->ar == COMIN) {
+		if (!context->comin_count) {
+			return 0xFF;
+		}
+		value = context->comin[(context->comin_write - context->comin_count)&sizeof(context->comin)];
+		context->comin_count--;
+		if (!context->comin_count) {
+			context->regs[IFSTAT] |= BIT_CMDI;
+		}
+		return value;
+	}
+	if (context->ar == STAT3) {
+		context->regs[IFSTAT] |= BIT_DECI;
+	}
+	if (context->ar >= sizeof(context->regs)) {
+		value = 0xFF;
+	} else {
+		value = context->regs[context->ar];
+	}
+	printf("CDC read %X: %X\n", context->ar, value);
+	context->ar++;
+	context->ar &= context->ar_mask;
+	return value;
+}
+
+void lc8951_ar_write(lc8951 *context, uint8_t value)
+{
+	context->ar = value & context->ar_mask;
+}
+
+//25 MHz clock input (1/2 SCD MCLK)
+//internal /2 divider
+//3 cycles for each SRAM access (though might be crystal frequency rather than internal frequency)
+//6 cycle period for DMA transfer out
+//
+
+void lc8951_run(lc8951 *context, uint32_t cycle)
+{
+	for(; context->cycle < cycle; context->cycle += context->clock_step)
+	{
+		if (context->cycle >= context->decode_end) {
+			context->decode_end = CYCLE_NEVER;
+			context->regs[IFSTAT] &= ~BIT_DECI;
+			context->regs[STAT3] &= ~BIT_VALST;
+			if (context->ctrl0 & BIT_WRRQ) {
+				uint16_t block_start = (context->regs[PTL] | (context->regs[PTH] << 8)) & (sizeof(context->buffer)-1);
+				for (int reg = HEAD0; reg < PTL; reg++)
+				{
+					printf("Setting HEAD%d to buffer[%X]\n", reg - HEAD0, block_start);
+					context->regs[reg] =context->buffer[block_start++];
+					block_start &= (sizeof(context->buffer)-1);
+				}
+			}
+			printf("Decode done %X:%X:%X mode %X\n", context->regs[HEAD0], context->regs[HEAD1], context->regs[HEAD2], context->regs[HEAD3]);
+			context->regs[STAT0] |= BIT_CRCOK;
+		}
+		if (context->transfer_end != CYCLE_NEVER) {
+			if (context->byte_handler(context->handler_data, context->buffer[context->dac & (sizeof(context->buffer)-1)])) {
+				context->dac++;
+				context->regs[DBCL]--;
+				if (context->regs[DBCL] == 0xFF) {
+					context->regs[DBCH]--;
+					if (context->regs[DBCH] == 0xFF) {
+						context->regs[IFSTAT] &= ~BIT_DTEI;
+						context->regs[IFSTAT] |= BIT_DTBSY;
+						if (context->cycle != context->transfer_end) {
+							printf("Expected transfer end at %u but ended at %u\n", context->transfer_end, context->cycle);
+						}
+						context->transfer_end = CYCLE_NEVER;
+					}
+				}
+			} else {
+				// pause transfer
+				context->transfer_end = CYCLE_NEVER;
+			}
+		}
+	}
+}
+
+void lc8951_resume_transfer(lc8951 *context, uint32_t cycle)
+{
+	if (context->transfer_end == CYCLE_NEVER && (context->ifctrl & BIT_DOUTEN)) {
+		uint16_t transfer_size = context->regs[DBCL] | (context->regs[DBCH] << 8);
+		if (transfer_size != 0xFFFF) {
+			//HACK!!! Work around Sub CPU running longer than we would like and dragging other components with it
+			uint32_t step_diff = (context->cycle - cycle) / context->clock_step;
+			if (step_diff) {
+				context->cycle -= step_diff * context->clock_step;
+			}
+			context->transfer_end = context->cycle + transfer_size * context->clock_step;
+			printf("RESUME: size %u, cycle %u, end %u\n", transfer_size, context->cycle, context->transfer_end);
+			if (step_diff) {
+				lc8951_run(context, cycle);
+			}
+		}
+	}
+}
+
+void lc8951_write_byte(lc8951 *context, uint32_t cycle, int sector_offset, uint8_t byte)
+{
+	lc8951_run(context, cycle);
+	uint16_t current_write_addr = context->regs[WAL] | (context->regs[WAH] << 8);
+	if (sector_offset == 12) {
+		//we've recevied the sync pattern for the next block
+
+		//header/status regs no longer considered "valid"
+		context->regs[STAT3] |= BIT_VALST;
+		//!DECI is set inactive at the same time as !VALST
+		context->regs[IFSTAT] |= BIT_DECI;
+		if (context->ctrl0 & BIT_DECEN) {
+			if (context->ctrl0 & BIT_WRRQ) {
+				uint16_t block_start = current_write_addr - 2352;
+				context->regs[PTL] = block_start;
+				context->regs[PTH] = block_start >> 8;
+			}
+			printf("Decoding block starting at %X\n", context->regs[PTL] | (context->regs[PTH] << 8));
+			//TODO: Datasheet has some hints about how long decoding takes in the form of how long DECI is asserted
+			context->decode_end = context->cycle + 2352 * context->clock_step * 4;
+		}
+	}
+	if (sector_offset >= 12 && sector_offset < 16) {
+		//TODO: Handle SHDREN = 1
+		if ((context->ctrl0 & (BIT_DECEN|BIT_WRRQ)) == (BIT_DECEN)) {
+			//monitor only mode
+			context->regs[HEAD0 + sector_offset - 12] = byte;
+		}
+	}
+	if ((context->ctrl0 & (BIT_DECEN|BIT_WRRQ)) == (BIT_DECEN|BIT_WRRQ)) {
+		context->buffer[current_write_addr & (sizeof(context->buffer)-1)] = byte;
+		context->regs[WAL]++;
+		if (!context->regs[WAL]) {
+			context->regs[WAH]++;
+		}
+	}
+}
+
+uint32_t lc8951_next_interrupt(lc8951 *context)
+{
+	if ((~context->regs[IFSTAT]) & context->ifctrl & (BIT_CMDI|BIT_DTEI|BIT_DECI)) {
+		//interrupt already pending
+		return context->cycle;
+	}
+	uint32_t deci_cycle = CYCLE_NEVER;
+	if (context->ifctrl & BIT_DECI) {
+		deci_cycle = context->decode_end;
+	}
+	uint32_t dtei_cycle = CYCLE_NEVER;
+	if (context->ifctrl & BIT_DTEI) {
+		dtei_cycle = context->transfer_end;
+	}
+	return deci_cycle < dtei_cycle ? deci_cycle : dtei_cycle;
+}
+
+void lc8951_adjust_cycles(lc8951 *context, uint32_t deduction)
+{
+	if (context->decode_end != CYCLE_NEVER) {
+		context->decode_end -= deduction;
+	}
+	if (context->transfer_end != CYCLE_NEVER) {
+		context->transfer_end -= deduction;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lc8951.h	Sun Jan 30 22:29:29 2022 -0800
@@ -0,0 +1,41 @@
+#ifndef LC8951_H_
+#define LC8951_H_
+
+#include <stdint.h>
+
+typedef uint8_t (*lcd8951_byte_recv_fun)(void *data, uint8_t byte);
+
+typedef struct {
+	lcd8951_byte_recv_fun byte_handler;
+	void     *handler_data;
+	uint32_t cycle;
+	uint32_t clock_step;
+	uint32_t decode_end;
+	uint32_t transfer_end;
+
+	uint8_t  buffer[0x4000];
+
+	uint8_t  regs[16];
+	uint8_t  comin[8];
+
+	uint16_t dac;
+	uint8_t  comin_write;
+	uint8_t  comin_count;
+	uint8_t  ifctrl;
+	uint8_t  ctrl0;
+	uint8_t  ctrl1;
+	uint8_t  ar;
+	uint8_t  ar_mask;
+} lc8951;
+
+void lc8951_init(lc8951 *context, lcd8951_byte_recv_fun byte_handler, void *handler_data);
+void lc8951_run(lc8951 *context, uint32_t cycle);
+void lc8951_reg_write(lc8951 *context, uint8_t value);
+uint8_t lc8951_reg_read(lc8951 *context);
+void lc8951_ar_write(lc8951 *context, uint8_t value);
+void lc8951_write_byte(lc8951 *context, uint32_t cycle, int sector_offset, uint8_t byte);
+uint32_t lc8951_next_interrupt(lc8951 *context);
+void lc8951_resume_transfer(lc8951 *context, uint32_t cycle);
+void lc8951_adjust_cycles(lc8951 *context, uint32_t deduction);
+
+#endif //LC8951_H_
--- a/m68k_core.c	Sat Jan 01 18:54:46 2022 -0800
+++ b/m68k_core.c	Sun Jan 30 22:29:29 2022 -0800
@@ -94,7 +94,7 @@
 void m68k_save_result(m68kinst * inst, m68k_options * opts)
 {
 	if (inst->dst.addr_mode != MODE_REG && inst->dst.addr_mode != MODE_AREG && inst->dst.addr_mode != MODE_UNUSED) {
-		if (inst->dst.addr_mode == MODE_AREG_PREDEC && 
+		if (inst->dst.addr_mode == MODE_AREG_PREDEC &&
 			((inst->src.addr_mode == MODE_AREG_PREDEC && inst->op != M68K_MOVE) || (inst->op == M68K_NBCD))
 		) {
 			areg_to_native(opts, inst->dst.params.regs.pri, opts->gen.scratch2);
@@ -489,7 +489,7 @@
 	}
 	opts->extra_code = opts->gen.code;
 	opts->gen.code = tmp;
-	
+
 	rts(&opts->extra_code);
 	return impl;
 }
@@ -499,7 +499,7 @@
 	code_info *code = &opts->gen.code;
 	uint8_t early_cycles;
 	uint16_t num_regs = inst->src.addr_mode == MODE_REG ? inst->src.params.immed : inst->dst.params.immed;
-	{	
+	{
 		//TODO: Move this popcount alg to a utility function
 		uint16_t a = (num_regs & 0b1010101010101010) >> 1;
 		uint16_t b = num_regs & 0b0101010101010101;
@@ -549,7 +549,7 @@
 			m68k_disasm(inst, disasm_buf);
 			fatal_error("%X: %s\naddress mode %d not implemented (movem dst)\n", inst->address, disasm_buf, inst->dst.addr_mode);
 		}
-		
+
 		cycles(&opts->gen, early_cycles);
 		if (num_regs <= 9) {
 			translate_movem_regtomem_reglist(opts, inst);
@@ -596,7 +596,7 @@
 			fatal_error("%X: %s\naddress mode %d not implemented (movem src)\n", inst->address, disasm_buf, inst->src.addr_mode);
 		}
 		cycles(&opts->gen, early_cycles);
-		
+
 		if (num_regs <= 9) {
 			translate_movem_memtoreg_reglist(opts, inst);
 		} else {
@@ -625,7 +625,7 @@
 static void translate_m68k_rte(m68k_options *opts, m68kinst *inst)
 {
 	m68k_trap_if_not_supervisor(opts, inst);
-	
+
 	code_info *code = &opts->gen.code;
 	//Read saved SR
 	areg_to_native(opts, 7, opts->gen.scratch1);
@@ -646,7 +646,7 @@
 code_ptr get_native_address(m68k_options *opts, uint32_t address)
 {
 	native_map_slot * native_code_map = opts->gen.native_code_map;
-	
+
 	memmap_chunk const *mem_chunk = find_map_chunk(address, &opts->gen, 0, NULL);
 	if (mem_chunk) {
 		//calculate the lowest alias for this address
@@ -680,7 +680,7 @@
 	} else {
 		address &= opts->gen.address_mask;
 	}
-	
+
 	uint32_t chunk = address / NATIVE_CHUNK_SIZE;
 	if (!native_code_map[chunk].base) {
 		return 0;
@@ -728,7 +728,7 @@
 	} else {
 		address &= opts->gen.address_mask;
 	}
-	
+
 	uint32_t chunk = address / NATIVE_CHUNK_SIZE;
 	if (!native_code_map[chunk].base) {
 		native_code_map[chunk].base = native_addr;
@@ -830,7 +830,7 @@
 		warning("Spurious breakpoing at %X\n", address);
 		remove_breakpoint(context, address);
 	}
-	
+
 	return context;
 }
 
@@ -958,15 +958,15 @@
 	}
 	code_ptr start = opts->gen.code.cur;
 	check_cycles_int(&opts->gen, inst->address);
-	
+
 	m68k_debug_handler bp;
 	if ((bp = find_breakpoint(context, inst->address))) {
 		m68k_breakpoint_patch(context, inst->address, bp, start);
 	}
-	
-	//log_address(&opts->gen, inst->address, "M68K: %X @ %d\n");
+
+	//log_address(&opts->gen, inst->address, opts->gen.clock_divider == 4 ? "Sub M68k: %X @ %d\n" : "Main M68K: %X @ %d\n");
 	if (
-		(inst->src.addr_mode > MODE_AREG && inst->src.addr_mode < MODE_IMMEDIATE) 
+		(inst->src.addr_mode > MODE_AREG && inst->src.addr_mode < MODE_IMMEDIATE)
 		|| (inst->dst.addr_mode > MODE_AREG && inst->dst.addr_mode < MODE_IMMEDIATE)
 		|| (inst->op == M68K_BCC && (inst->src.params.immed & 1))
 	) {
@@ -1196,7 +1196,7 @@
 	context->aregs[7] = reset_vec[0] << 16 | reset_vec[1];
 	uint32_t address = reset_vec[2] << 16 | reset_vec[3];
 	//interrupt mask may have changed so force a sync
-	sync_components(context, address);
+	context->options->sync_components(context, address);
 	start_68k_context(context, address);
 }
 
--- a/m68k_core.h	Sat Jan 01 18:54:46 2022 -0800
+++ b/m68k_core.h	Sun Jan 30 22:29:29 2022 -0800
@@ -25,6 +25,8 @@
 #define M68K_STATUS_TRACE 0x80
 
 typedef void (*start_fun)(uint8_t * addr, void * context);
+typedef struct m68k_context m68k_context;
+typedef m68k_context *(*sync_fun)(m68k_context * context, uint32_t address);
 
 typedef struct {
 	code_ptr impl;
@@ -59,6 +61,7 @@
 	code_ptr		set_sr;
 	code_ptr		set_ccr;
 	code_ptr        bp_stub;
+	sync_fun        sync_components;
 	code_info       extra_code;
 	movem_fun       *big_movem;
 	uint32_t        num_movem;
@@ -66,7 +69,6 @@
 	code_word       prologue_start;
 } m68k_options;
 
-typedef struct m68k_context m68k_context;
 typedef void (*m68k_debug_handler)(m68k_context *context, uint32_t pc);
 
 typedef struct {
@@ -106,7 +108,7 @@
 void translate_m68k_stream(uint32_t address, m68k_context * context);
 void start_68k_context(m68k_context * context, uint32_t address);
 void resume_68k(m68k_context *context);
-void init_m68k_opts(m68k_options * opts, memmap_chunk * memmap, uint32_t num_chunks, uint32_t clock_divider);
+void init_m68k_opts(m68k_options * opts, memmap_chunk * memmap, uint32_t num_chunks, uint32_t clock_divider, sync_fun sync_components);
 m68k_context * init_68k_context(m68k_options * opts, m68k_reset_handler reset_handler);
 void m68k_reset(m68k_context * context);
 void m68k_options_free(m68k_options *opts);
--- a/m68k_core_x86.c	Sat Jan 01 18:54:46 2022 -0800
+++ b/m68k_core_x86.c	Sun Jan 30 22:29:29 2022 -0800
@@ -422,8 +422,8 @@
 		}
 		dec_amount = inst->extra.size == OPSIZE_WORD ? 2 : (inst->extra.size == OPSIZE_LONG ? 4 : (op->params.regs.pri == 7 ? 2 :1));
 		if (!dst || (
-			inst->op != M68K_MOVE && inst->op != M68K_MOVEM 
-			&& inst->op != M68K_SUBX && inst->op != M68K_ADDX 
+			inst->op != M68K_MOVE && inst->op != M68K_MOVEM
+			&& inst->op != M68K_SUBX && inst->op != M68K_ADDX
 			&& inst->op != M68K_ABCD && inst->op != M68K_SBCD
 		)) {
 			cycles(&opts->gen, PREDEC_PENALTY);
@@ -817,7 +817,7 @@
 void translate_m68k_bcc(m68k_options * opts, m68kinst * inst)
 {
 	code_info *code = &opts->gen.code;
-	
+
 	int32_t disp = inst->src.params.immed;
 	uint32_t after = inst->address + 2;
 	if (inst->extra.cond == COND_TRUE) {
@@ -827,11 +827,11 @@
 		uint8_t cond = m68k_eval_cond(opts, inst->extra.cond);
 		code_ptr do_branch = code->cur + 1;
 		jcc(code, cond, do_branch);
-		
+
 		cycles(&opts->gen, inst->variant == VAR_BYTE ? 8 : 12);
 		code_ptr done = code->cur + 1;
 		jmp(code, done);
-		
+
 		*do_branch = code->cur - (do_branch + 1);
 		cycles(&opts->gen, 10);
 		code_ptr dest_addr = get_native_address(opts, after + disp);
@@ -841,7 +841,7 @@
 			dest_addr = code->cur + 256;
 		}
 		jmp(code, dest_addr);
-		
+
 		*done = code->cur - (done + 1);
 	}
 }
@@ -1310,7 +1310,7 @@
 {
 	code_info *code = &opts->gen.code;
 	uint8_t size = inst->dst.addr_mode == MODE_AREG ? OPSIZE_LONG : inst->extra.size;
-	
+
 	uint32_t numcycles;
 	if ((inst->op == M68K_ADDX || inst->op == M68K_SUBX) && inst->src.addr_mode != MODE_REG) {
 		numcycles = 4;
@@ -1322,7 +1322,7 @@
 		} else if (inst->dst.addr_mode == MODE_REG) {
 			numcycles = inst->src.addr_mode <= MODE_AREG || inst->src.addr_mode == MODE_IMMEDIATE ? 8 : 6;
 		} else if (inst->dst.addr_mode == MODE_AREG) {
-			numcycles = numcycles = inst->src.addr_mode <= MODE_AREG || inst->src.addr_mode == MODE_IMMEDIATE  
+			numcycles = numcycles = inst->src.addr_mode <= MODE_AREG || inst->src.addr_mode == MODE_IMMEDIATE
 				|| inst->extra.size == OPSIZE_WORD ? 8 : 6;
 		} else {
 			numcycles = 4;
@@ -1331,11 +1331,11 @@
 		numcycles = 4;
 	}
 	cycles(&opts->gen, numcycles);
-	
+
 	if (inst->op == M68K_ADDX || inst->op == M68K_SUBX) {
 		flag_to_carry(opts, FLAG_X);
 	}
-	
+
 	if (src_op->mode == MODE_REG_DIRECT) {
 		if (dst_op->mode == MODE_REG_DIRECT) {
 			op_rr(code, inst, src_op->base, dst_op->base, size);
@@ -1500,7 +1500,7 @@
 		//destination is in memory so we need to preserve scratch2 for the write at the end
 		push_r(code, opts->gen.scratch2);
 	}
-	
+
 	//reg to reg takes 6 cycles, mem to mem is 4 cycles + all the operand fetch/writing (including 2 cycle predec penalty for first operand)
 	cycles(&opts->gen, inst->dst.addr_mode != MODE_REG ? BUS : BUS + 2);
 	uint8_t other_reg;
@@ -1762,7 +1762,7 @@
 		force = dividend >> 31;
 		quotient = quotient << 1 | bit;
 		dividend = dividend << 1;
-		
+
 		if (force || dividend >= divisor_shift) {
 			dividend -= divisor_shift;
 			cycles += force ? 4 : 6;
@@ -1784,7 +1784,7 @@
 	if (divisor_shift & 0x80000000) {
 		divisor_shift = 0 - divisor_shift;
 	}
-	
+
 	uint32_t cycles = 12;
 	if (dividend & 0x80000000) {
 		//dvs10
@@ -1805,7 +1805,7 @@
 	{
 		quotient = quotient << 1 | bit;
 		dividend = dividend << 1;
-		
+
 		if (dividend >= divisor_shift) {
 			dividend -= divisor_shift;
 			cycles += 6;
@@ -1824,7 +1824,7 @@
 		quotient = quotient << 1;
 	}
 	cycles += 4;
-	
+
 	context->flags[FLAG_V] = 0;
 	if (orig_divisor & 0x80000000) {
 		cycles += 16; //was 10
@@ -1894,7 +1894,7 @@
 	cmp_ir(code, 0, opts->gen.scratch1, SZ_D);
 	code_ptr not_zero = code->cur+1;
 	jcc(code, CC_NZ, not_zero);
-	
+
 	//TODO: Check that opts->trap includes the cycles conumed by the first trap0 microinstruction
 	cycles(&opts->gen, 4);
 	uint32_t isize = 2;
@@ -1917,7 +1917,7 @@
 	mov_ir(code, VECTOR_INT_DIV_ZERO, opts->gen.scratch2, SZ_D);
 	mov_ir(code, inst->address+isize, opts->gen.scratch1, SZ_D);
 	jmp(code, opts->trap);
-	
+
 	*not_zero = code->cur - (not_zero + 1);
 	code_ptr end = NULL;
 	if (inst->op == M68K_DIVU) {
@@ -1926,13 +1926,13 @@
 		cmp_rr(code, opts->gen.scratch1, opts->gen.scratch2, SZ_D);
 		code_ptr not_overflow = code->cur+1;
 		jcc(code, CC_C, not_overflow);
-		
+
 		//overflow seems to always set the N and clear Z
 		update_flags(opts, N1|Z0|V1);
 		cycles(&opts->gen, 10);
 		end = code->cur+1;
 		jmp(code, end);
-		
+
 		*not_overflow = code->cur - (not_overflow + 1);
 	}
 	call(code, opts->gen.save_context);
@@ -1941,14 +1941,14 @@
 	call_args(code, (code_ptr)(inst->op == M68K_DIVU ? divu : divs), 3, opts->gen.scratch2, opts->gen.context_reg, opts->gen.scratch1);
 	pop_r(code, opts->gen.context_reg);
 	mov_rr(code, RAX, opts->gen.scratch1, SZ_D);
-	
+
 	call(code, opts->gen.load_context);
-	
+
 	if (inst->op == M68K_DIVU) {
 		cmp_ir(code, 0, opts->gen.scratch1, SZ_W);
 		update_flags(opts, V0|Z|N);
 	}
-	
+
 	if (dst_op->mode == MODE_REG_DIRECT) {
 		mov_rr(code, opts->gen.scratch1, dst_op->base, SZ_D);
 	} else {
@@ -2048,7 +2048,7 @@
 		call(code, opts->gen.load_context);
 		pop_r(code, opts->gen.scratch1);
 	}
-	
+
 	uint8_t dst_reg;
 	if (dst_op->mode == MODE_REG_DIRECT) {
 		dst_reg = dst_op->base;
@@ -2217,11 +2217,11 @@
 	bt_irdisp(code, BIT_SUPERVISOR, opts->gen.context_reg, offsetof(m68k_context, status), SZ_B);
 	code_ptr in_sup_mode = code->cur + 1;
 	jcc(code, CC_C, code->cur + 2);
-	
+
 	ldi_native(opts, VECTOR_PRIV_VIOLATION, opts->gen.scratch2);
 	ldi_native(opts, inst->address, opts->gen.scratch1);
 	jmp(code, opts->trap);
-	
+
 	*in_sup_mode = code->cur - (in_sup_mode + 1);
 }
 
@@ -2544,11 +2544,11 @@
 	m68k_options * opts = context->options;
 	code_info native;
 	native.cur = native_addr ? native_addr : get_native_address(context->options, address);
-	
+
 	if (!native.cur) {
 		return;
 	}
-	
+
 	if (*native.cur != opts->prologue_start) {
 		//instruction has already been patched, probably for retranslation
 		return;
@@ -2557,12 +2557,12 @@
 	native.stack_off = 0;
 	code_ptr start_native = native.cur;
 	mov_ir(&native, address, opts->gen.scratch1, SZ_D);
-	
-	
+
+
 	call(&native, opts->bp_stub);
 }
 
-void init_m68k_opts(m68k_options * opts, memmap_chunk * memmap, uint32_t num_chunks, uint32_t clock_divider)
+void init_m68k_opts(m68k_options * opts, memmap_chunk * memmap, uint32_t num_chunks, uint32_t clock_divider, sync_fun sync_components)
 {
 	memset(opts, 0, sizeof(*opts));
 	opts->gen.memmap = memmap;
@@ -2612,6 +2612,7 @@
 	opts->gen.limit = RBP;
 	opts->gen.scratch1 = RCX;
 	opts->gen.align_error_mask = 1;
+	opts->sync_components = sync_components;
 
 
 	opts->gen.native_code_map = malloc(sizeof(native_map_slot) * NATIVE_MAP_CHUNKS);
@@ -2695,7 +2696,7 @@
 	push_r(code, opts->gen.scratch1);
 
 	xor_rr(code, opts->gen.scratch1, opts->gen.scratch1, SZ_D);
-	call_args_abi(code, (code_ptr)sync_components, 2, opts->gen.context_reg, opts->gen.scratch1);
+	call_args_abi(code, (code_ptr)opts->sync_components, 2, opts->gen.context_reg, opts->gen.scratch1);
 	pop_r(code, RSI); //restore saved address from opts->gen.scratch1
 	push_r(code, RAX); //save context pointer for later
 	call_args(code, (code_ptr)get_native_address_trans, 2, RAX, RSI);
@@ -2713,7 +2714,7 @@
 	push_r(code, opts->gen.scratch2);
 	call(code, opts->gen.save_context);
 	xor_rr(code, opts->gen.scratch1, opts->gen.scratch1, SZ_D);
-	call_args_abi(code, (code_ptr)sync_components, 2, opts->gen.context_reg, opts->gen.scratch1);
+	call_args_abi(code, (code_ptr)opts->sync_components, 2, opts->gen.context_reg, opts->gen.scratch1);
 	mov_rr(code, RAX, opts->gen.context_reg, SZ_PTR);
 	call(code, opts->gen.load_context);
 	pop_r(code, opts->gen.scratch2);
@@ -2722,14 +2723,14 @@
 	retn(code);
 
 	opts->gen.handle_code_write = (code_ptr)m68k_handle_code_write;
-	
+
 	check_alloc_code(code, 256);
 	opts->gen.handle_align_error_write = code->cur;
 	code->cur += 256;
 	check_alloc_code(code, 256);
 	opts->gen.handle_align_error_read = code->cur;
 	code->cur += 256;
-	
+
 	opts->read_16 = gen_mem_fun(&opts->gen, memmap, num_chunks, READ_16, NULL);
 	opts->read_8 = gen_mem_fun(&opts->gen, memmap, num_chunks, READ_8, NULL);
 	opts->write_16 = gen_mem_fun(&opts->gen, memmap, num_chunks, WRITE_16, NULL);
@@ -2830,7 +2831,7 @@
 		}
 	}
 	retn(code);
-	
+
 	code_info tmp_code = *code;
 	code->cur = opts->gen.handle_align_error_write;
 	code->last = code->cur + 256;
@@ -2893,7 +2894,7 @@
 	call(code, opts->native_addr_and_sync);
 	cycles(&opts->gen, 18);
 	jmp_r(code, opts->gen.scratch1);
-	
+
 	code->cur = opts->gen.handle_align_error_read;
 	code->last = code->cur + 256;
 	//unwind the stack one functinon call
@@ -2955,7 +2956,7 @@
 	call(code, opts->native_addr_and_sync);
 	cycles(&opts->gen, 18);
 	jmp_r(code, opts->gen.scratch1);
-	
+
 	*code = tmp_code;
 
 	opts->gen.handle_cycle_limit_int = code->cur;
@@ -2974,16 +2975,16 @@
 	*no_trace = code->cur - (no_trace + 1);
 	//handle interrupts
 	cmp_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, int_cycle), opts->gen.cycles, SZ_D);
-	code_ptr do_int = code->cur + 2; 
+	code_ptr do_int = code->cur + 2;
 	jcc(code, CC_NC, do_int+512);//force 32-bit displacement
 	//handle component synchronization
 	cmp_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, sync_cycle), opts->gen.cycles, SZ_D);
 	skip_sync = code->cur + 1;
 	jcc(code, CC_C, code->cur + 2);
 	call(code, opts->gen.save_context);
-	call_args_abi(code, (code_ptr)sync_components, 2, opts->gen.context_reg, opts->gen.scratch1);
+	call_args_abi(code, (code_ptr)opts->sync_components, 2, opts->gen.context_reg, opts->gen.scratch1);
 	mov_rr(code, RAX, opts->gen.context_reg, SZ_PTR);
-	jmp(code, opts->gen.load_context);
+	call(code, opts->gen.load_context);
 	*skip_sync = code->cur - (skip_sync+1);
 	cmp_irdisp(code, 0, opts->gen.context_reg, offsetof(m68k_context, should_return), SZ_B);
 	code_ptr do_ret = code->cur + 1;
@@ -3031,9 +3032,9 @@
 	pop_r(code, opts->gen.scratch2);
 	add_ir(code, 16-sizeof(void *), RSP, SZ_PTR);
 	jmp_r(code, opts->gen.scratch1);
-	
+
 	code->stack_off = tmp_stack_off;
-	
+
 	*((uint32_t *)do_int) = code->cur - (do_int+4);
 	//implement 1 instruction latency
 	cmp_irdisp(code, INT_PENDING_NONE, opts->gen.context_reg, offsetof(m68k_context, int_pending), SZ_B);
@@ -3048,10 +3049,10 @@
 	cmp_irdisp(code, INT_PENDING_SR_CHANGE, opts->gen.context_reg, offsetof(m68k_context, int_pending), SZ_B);
 	code_ptr already_int_num = code->cur + 1;
 	jcc(code, CC_NZ, already_int_num);
-	
+
 	mov_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, int_num), opts->gen.scratch2, SZ_B);
 	mov_rrdisp(code, opts->gen.scratch2, opts->gen.context_reg, offsetof(m68k_context, int_pending), SZ_B);
-	
+
 	*already_int_num = code->cur - (already_int_num + 1);
 	//save PC as stored in scratch1 for later
 	push_r(code, opts->gen.scratch1);
@@ -3133,10 +3134,10 @@
 	add_ir(code, 16-sizeof(void *), RSP, SZ_PTR);
 	jmp_r(code, opts->gen.scratch1);
 	code->stack_off = tmp_stack_off;
-	
+
 	opts->handle_int_latch = code->cur;
 	cmp_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, int_cycle), opts->gen.cycles, SZ_D);
-	code_ptr do_latch = code->cur + 1; 
+	code_ptr do_latch = code->cur + 1;
 	jcc(code, CC_NC, do_latch);
 	retn(code);
 	*do_latch = code->cur - (do_latch + 1);
@@ -3177,7 +3178,7 @@
 	call(code, opts->native_addr_and_sync);
 	cycles(&opts->gen, 18);
 	jmp_r(code, opts->gen.scratch1);
-	
+
 	opts->retrans_stub = code->cur;
 	call(code, opts->gen.save_context);
 	push_r(code, opts->gen.context_reg);
@@ -3186,8 +3187,8 @@
 	mov_rr(code, RAX, opts->gen.scratch1, SZ_PTR);
 	call(code, opts->gen.load_context);
 	jmp_r(code, opts->gen.scratch1);
-	
-	
+
+
 	check_code_prologue(code);
 	opts->bp_stub = code->cur;
 
@@ -3224,6 +3225,6 @@
 	add_ir(code, check_int_size - patch_size, opts->gen.scratch1, SZ_PTR);
 	jmp_r(code, opts->gen.scratch1);
 	code->stack_off = tmp_stack_off;
-	
+
 	retranslate_calc(&opts->gen);
 }
--- a/m68k_internal.h	Sat Jan 01 18:54:46 2022 -0800
+++ b/m68k_internal.h	Sun Jan 30 22:29:29 2022 -0800
@@ -106,8 +106,6 @@
 #define PREDEC_PENALTY 2
 extern char disasm_buf[1024];
 
-m68k_context * sync_components(m68k_context * context, uint32_t address);
-
 void m68k_invalid();
 void bcd_add();
 void bcd_sub();
--- a/notes.txt	Sat Jan 01 18:54:46 2022 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-cmp.w <ea>, Dn 4(1/0) + <ea> time
-cmp.l <ea>, Dn 6(1/0) + <ea> time
-cmp.w #num, Dn 4(1/0) + 4(1/0)
-cmp.l #num, Dn 6(1/0) + 8(2/0)
-
-cmpi.w #num, Dn 8(2/0)
-cmpi.l #num, Dn 14(3/0)
-
-
-movem
-
-subtype field (bits 9-11) = 110 or 100 depending on direction
-bit 8 = 0
-bit 7 = 1
-bit 6 = size
-
-
-
-x86-64 registers in 68K core
-
-1. native stack pointer
-2. current cycle count
-3. target cycle count
-4. cartridge address
-5. work ram address
-6. scratch register
-7. context pointer (contains 68K registers and memory pointers not in registers)
-8. status register (maybe, depends on how well I can abuse native x86 status stuff)
-Rest of registers used for holding 68K registers
-
-rax = cycle counter
-bl = N flag
-bh = V flag
-rcx = scratch register
-dl = Z flag
-dh = C flag
-rbp = target cycle count
-rsi = context pointer
-rdi = scratch register
-r8 = cartridge address
-r9 = work ram address
-r10 = d0
-r11 = d1
-r12 = d2
-r13 = a0
-r14 = a1
-r15 = a7
-rsp = native stack pointer
-
-68K context:
-uint8_t flags[5];
-uint8_t pad??[3]
-uint32_t dregs[8]; //8 + 4 * reg
-uint32_t aregs[8]; //40 + 4 * reg
-.....
-
-x86-64 registers in Z80 core
-
-ax = HL
-bx = BC
-cx = DE
-dx = IX
-ebp = current cycle count
-rsi = context pointer
-edi = target cycle count
-rsp = native stack pointer
-r8 = IY
-r9 = SP
-r10 = A (maybe AF?)
-r11 = z80 ram address
-r12 = cartridge address if bank is pointed at ROM
-r13 = scratch1
-r14 = scratch2
-r15 = ?maybe z80 bank register?
-
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/notes/cdd_mcu_notes.txt	Sun Jan 30 22:29:29 2022 -0800
@@ -0,0 +1,429 @@
+CD Block Clock: 16.9344 MHz
+MCU Clock divider: 2.1168 MHz (cd clock / 8)
+
+State at Reset:
+	Status buffer is filled with zero
+	Mechanism is reset
+	Emulation state set to ES_CLOSING
+
+HSCK active low
+IRQ active high
+CDCK active high
+
+Main Loop:
+	wait for subcode data for next frame
+		seems to be delivered via a "bank" IRQ
+	if servo is not in PLAY state
+		set DOUT and EMP to inactive
+		set MUTE to active
+	elif Q subcode CRC is not valid
+		set MUTE to active
+	elif Q subcode CONTROL field indicates a DATA track
+		set DOUT and MUTE to active
+		set EMP to inactive
+	else
+		set DOUT and MUTE to inactive
+		set EMP based on emphasis bit in Q subcode CONTROL field
+	run drive emulation state machine
+	if we received a valid command last loop
+		populate status buffer based on request format (see table below)
+		if drive error status is valid
+			override status format (2nd nibble) to RF_NOTREADY
+			put error status in STATUS (1st nibble) of status buffer
+			set error status valid to false
+		else
+			put drive status in STATUS (1st nibble) of status buffer
+	Communication with Host via gate array:
+		goes to fail state if HSCK is active (i.e. low)
+		IRQ line is asserted
+		Waits ~2ms (4234 cycles) for HSCK to go active (i.e lo)
+			on timeout, resets outputs to host to inactive and quits comms this loop
+		deasserts irq
+		Sends 10 status nibbles
+			nibbles are inverted
+			HSCK is handshake from host
+			CDCK is handshake frm MCU
+			minimum 77 cycles per nibble
+
+			HSCK 1 (inactive) -> 0 (active)
+			nibble output
+			CDCK 0 (inactive) ->1 (active)
+			HSCK 0->1
+			CDCK 1->0
+		sets status sent flag
+		Gets 10 command nibbles
+			HSCK 1->0
+			nibble input
+			CDCK 0->1
+			HSCK 0->1
+			CDCK 1->0
+		Entire status/command transfer has timeout of ~3ms (6351 cycles)
+			on timeout, resets outputs to host to inactive and quits comms this loop
+		sets command received flag
+		Checksum of command is checked
+			if bad, resets outputs to host to inactive and quits comms this loop
+		set command valid flag
+		resets outputs to host to inactive
+	Runs command from host
+		Generally comms failures cause it to just run a Nop command
+		checksum failures cause drive error status to be set to STATUS_SUMERROR
+		command is specified by first 2 nibbles of command packet
+		second nibble must be zero or command is invalid
+		first nibble must be < 0xE and != 0x5 or command is invalid
+		command is sent to PC
+		command implementation is run
+
+
+format Absolute
+	if Q subcode CRC is bad OR Q subcode Control ADR mode is not 1 or Q subcode tracknum indicates leadin
+		set status format (2nd nibble) to RF_NOTREADY
+	else
+		store Absolute (RF_A == 0) in status format (2nd nibble)
+		copy ATIME in BCD format from Q subcode buffer to 3rd through 8th nibble (inclusive)
+		store flags in 9th nibble
+			these seem to correspond to the data, emphasis and mute control bits sent to CD hardware
+format Relative
+	if Q subcode CRC is bad OR Q subcode Control ADR mode is not 1 or Q subcode tracknum indicates leadin
+		set status format (2nd nibble) to RF_NOTREADY
+	else
+		store Relative (RF_R == 1) in status format (2nd nibble)
+		copy relative track TIME in BCD format from Q subcode buffer to 3rd through 8th nibble (inclusive)
+		store flags in 9th nibble
+			these seem to correspond to the data, emphasis and mute control bits sent to CD hardware
+format Track
+	if Q subcode CRC is bad OR Q subcode Control ADR mode is not 1 or Q subcode tracknum indicates leadin
+		set status format (2nd nibble) to RF_NOTREADY
+	else
+		store Track (RF_T == 2) in status format (2nd nibble)
+		copy track number to 3rd and 4th status buffer nibbles
+		copy Q subcode CONTROL field to 5th status buffer nibble
+		copy Q subcode ADR field to 6th status buffer nibble
+		store flags in 9th nibble
+			these seem to correspond to the data, emphasis and mute control bits sent to CD hardware
+format TOCO
+	if disc is not validated
+		set status format (2nd nibble) to RF_NOTREADY
+	else
+		set status format (2nd nibble) to RF_TOCO(3)
+		copy leadout start in MM:SS:FF format (BCD) to 3rd through 8th nibble (inclusive)
+	store flags in 9th nibble
+		these seem to correspond to the data, emphasis and mute control bits sent to CD hardware
+format TOCT
+	if disc is not validated
+		set status format (2nd nibble) to RF_NOTREADY
+	else
+		set status format (2nd nibble) to RF_TOCT(4)
+		copy first track number in BCD format to 3rd and 4th nibbles
+		copy last track number in BCD format to 5th and 6th nibbles
+		copy TOCTVERSION(0) in BCD format to 7th and 8th nibbles
+	store flags in 9th nibble
+		these seem to correspond to the data, emphasis and mute control bits sent to CD hardware
+format TOCN
+	if disc is not validated
+		set status format (2nd nibble) to RF_NOTREADY
+		store flags in 9th nibble
+			these seem to correspond to the data, emphasis and mute control bits sent to CD hardware
+	else
+		set status format (2nd nibble) to RF_TOCN(5)
+		if requested trac is not valid
+			set status format (2nd nibble) to RF_NOTREADY(F)
+			store flags in 9th nibble
+				these seem to correspond to the data, emphasis and mute control bits sent to CD hardware
+		else
+			copy track start time in MM:SS:FF format (BCD) to 3rd through 8th nibble (includisve)
+			if track is a data track, force MSB of frame to 1
+				notably, since frame numbers sould be <75 this bit would always be zero otherwise
+			store low nibble of BCD track number in 8th nibble
+format Error
+	set status format (2nd nibble) to RF_E(6)
+	error number is placed in 3rd nibble
+		note emulator always writes 0
+	store flags in 9th nibble
+		these seem to correspond to the data, emphasis and mute control bits sent to CD hardware
+
+state Stopping:
+	if servo is in STOP status
+		if disc is known present or disc status is unknown
+			set drive status to STOP
+		else
+			set drive status to NODISC
+state TOCing:
+	if servo is in STOP status
+		if focus is in NOTFOCUSING status
+			set haveDisc to FALSE
+			set haveDiscValid to TRUE
+			set drive status to NODISC
+		else
+			do nothing this tick
+	elif haveDisc is FALSE
+		do nothing this tick (comments suggest this is an abnormal situation)
+	else
+		set drive status to TOCREAD
+		if servo is NOT in PLAY status
+			stop here
+		if haveTOCValid is FALSE
+			process current Q subcode data for TOC entry (see GetTOC)
+			if TOC reading is done
+				set haveTOCValid to true
+				SEEK command is set to mechanism for PBA of program area start
+				stop until next tick
+			else
+				stop until next tick
+		else
+			if havePAValid is TRUE (comments say this isn't supposed to happen)
+				PAUSE command is sent to mechanism
+			else
+				check Q subcode data to see if we have found the start of the Program Area
+				if we have
+					set havePAValid to TRUE
+					SEEK command for start of first track is sent to mechanism
+					PAUSE command is sent to mechanism
+				else if current block Q subcode is invalid or not Mode 1
+					SEEK to next block
+				else
+					SEEK forward 250 blocks
+state Reading:
+state Seeking:
+state Pausing:
+state Playing:
+state Cuing:
+	These 5 states share a common implementation
+	if servo status is STOP
+		do nothing
+	elif servo status is SEEK
+		if mechanism error status is NOERROR
+			set drive status to SEEK
+		else
+			STOP command sent to mechanism
+			emulation state set to STOPPING
+	else
+		if servo status is PAUSE
+			if drive status is NOT DISCEND
+				set drive status to PAUSE
+		else
+			if Q subcode CRC is bad OR Q Control ADR is not mode 1 OR Q subcode tracknum is not lead out
+				set drive status to PLAY
+			else
+				PAUSE command sent to mechanism
+				set drive status to DISCEND
+
+state FWDing:
+	if servo status is STOP
+		do nothing
+	elif servo status is SEEK
+		if mechanism error status is NOERROR
+			set drive status to SCAN
+		else
+			STOP command is sent to mechanism
+			emulation state is set to STOPPING
+	elif servo status is PAUSE
+		set drive status to DISCEND
+	else:
+		set drive status to SCAN
+		if Q subcode CRC is valid AND Q Control ADR is mode 1
+			if Q subcode tracknum indicates leadout
+				PAUSE command is sent to mechanism
+				set drive status to DISCEND
+			else
+				if scanClock is 0
+					get ATIME from current Q subcode block
+					turn it into an LBA, then PBA
+					Add 100 to it
+					SEEK command sent to mechanism
+					reset scanClock to SCANPLAYTIME(10)
+state RVSing:
+	if servo status is STOP
+		do nothing
+	elif servo status is SEEK
+		if mechanism error status is NOERROR
+			set drive status to SCAN
+		else
+			STOP command is sent to mechanism
+			emulation state is set to STOPPING
+	elif servo status is PAUSE
+		do nothing
+	else
+		set drive status to SCAN
+		if Q subcode CRC is valid AND Q Control ADR is mode 1
+			if Q subcode tracknum indcates we're in the leadin area
+				SEEK command is sent to mechanism for beginning of first track
+				PLAY command is sent to mechanism
+				Set emulation state to PLAYING
+			else
+				if scanClock is 0
+					get ATIME from current Q subcode block
+					turn it into an LBA, then PBA
+					subtract 140 from it
+					SEEK command sent to mechanism
+					reset scanClock to SCANPLAYTIME(10)
+state Skipping:
+	if servo status is STOP
+		do nothing
+	elif servo status is SEEK
+		if mechanism error status is NOERROR
+			set drive status to TRACKING
+		else
+			STOP command is sent to mechanism
+			emulation state is set to STOPPING
+	elif servo status is PAUSE
+		set drive status to PAUSE
+	else
+		PAUSE command sent to mechanism
+state DoorClosing:
+	if door state is CLOSING
+		set drive status to TRAYMOVING
+	elif door state is CLOSED
+		set drive status to STOP
+		set request format to absolute
+		set emulation state to CLOSED
+state DoorClosed:
+	do nothing
+state DoorOpening:
+	if door state is OPENING
+		set drive status to TRAYMOVING
+	elif door state is OPEN
+		set drive status to DOOROPEN
+		set emulation state to OPEN
+state DoorOpen:
+	if door status is NOT one of OPEN or OPENING
+		set emulation state to CLOSING
+
+command Nop
+command Stop
+	set request format to absolute
+	if drive status is not DOOROPEN and status is notTRAYMOVING
+		send stop command to mechanism
+		set emulation state to ES_STOPPING
+command Report Request
+	4th nibble specifies request type
+	RF_A(0)
+		set absolute time format
+	RF_R(1)
+		set relative time format
+	RF_T(2)
+		request current track info
+	RF_TOCO(3)
+		request disc completion time
+		will be ignored if disc has not been validated yet
+			drive error status will be set to COMMANDERROR
+			request format will be set back to absolute
+	RF_TOCT(4)
+		request start/end tracks on disc
+		if disc is validated and drive is stopped
+			will seek to start of disc and pause there
+			emulation state set to TOCing
+		if disc is validated and drive is not stopped
+			only request format is changed
+		else
+			prepares memory state for TOC read
+			will seek to start of disc and tell mechanism to play
+			emulation state set to TOCing
+	RF_TOCN(5)
+		request start time of track N
+		if drivestatus is DOOROPEN, TRAYMOVING or STOP
+			command will be ignore dand drive error status will be set to COMMANDERROR
+			request format will be set back to absolute
+		requested track number is in the 5th and 6th nibbles in BCD format
+		SEEK command sent to mechanism for start of disc
+		PAUSE command sent to mechansim
+		emulation state set to TOCing
+	RF_E(6)
+		request error info
+
+command Read
+	if drive status is DOOROPEN or TRAYMOVING
+		set drive error status to COMMANDERROR and stop processing command
+	check request format, if it's TOCT or TOCN, force it to absolute
+	if disc is not validated
+		set drive error status to COMMANDERROR and stop processing command
+	location specified in MM:SS:FF format (BCD) in the 3rd through 8th nibbles (inclusive)
+	location is converted to an LBA
+	if location >= leadout start
+		set drive error status to COMMANDERROR and stop processing command
+	program area offset is added to location to convert it to a physical block address
+	PBA is adjusted back 4
+	SEEK command is sent to mechanism for adjusted PBA
+	PLAY command is sent to mechanism
+	Emulation state set to Reading
+command Seek
+	Same as Read, except sends Pause instead of Play for second command to mechanism
+	Emulation state set to Seeking
+command Pause
+	if drive status is DOOROPEN or TRAYMOVING or SCAN
+		set drive error status to COMMANDERROR and stop processing command
+	check request format, if it's TOCT or TOCN, force it to absolute
+	if disc is not validated
+		set drive error status to COMMANDERROR and stop processing command
+	if drive status is STOP
+		calculates PBA of first track from TOC
+		PBA is NOT adjusted back
+		send SEEK command to mechanism for calculated PBA
+	send PAUSE to mechanism
+	set emulation state to Pausing
+command Play
+	if drive status is DOOROPEN or TRAYMOVING or DISCEND or DISCIN
+		set drive error status to COMMANDERROR and stop processing command
+	check request format, if it's TOCT or TOCN, force it to absolute
+	if disc is not validated
+		set drive error status to COMMANDERROR and stop processing command
+	if drive status is TOCREAD or STOP
+		calculates PBA of first track from TOC
+		PBA is adjusted back by 4
+		SEEK command is sent to mechanism for adjusted PBA
+	send PLAY command to mechanism
+	set emulation state to Playing
+command Fwd
+	if drive status is NOT one of PLAY, PAUSE or DISCIN
+		set drive error status to COMMANDERROR and stop processing command
+	check request format, if it's TOCT or TOCN, force it to absolute
+	if disc is not validated
+		set drive error status to COMMANDERROR and stop processing command
+	Send PLAY command to mechanism
+	Emulation state set to Fwding
+	scanClock is set to SCANPLAYTIME(10)??
+command Rvs
+	if drive status is NOT one of PLAY, PAUSE or DISCEND
+		set drive error status to COMMANDERROR and stop processing command
+	check request format, if it's TOCT or TOCN, force it to absolute
+	if disc is not validated
+		set drive error status to COMMANDERROR and stop processing command
+	Send PLAY command to mechanism
+	Emulation state set to Rvsing
+command TrackSkip
+	if drive status is NOT one of PLAY, PAUSE or DISCEND
+		set drive error status to COMMANDERROR and stop processing command
+	check request format, if it's TOCT or TOCN, force it to absolute
+	if disc is not validated
+		set drive error status to COMMANDERROR and stop processing command
+	direction to skip is in 4th nibble of command
+	number of tracks to skip is a 16-bit value in the 5th-8th nibbles
+		note tese are physical tracks i.e. loops of the spiral
+	SKIP command is sent to mechanism
+	PAUSE command is sent to mechanism
+	emulation state is set to TSkping
+command TrackCue
+	if drive status is NOT one of STOP, PLAY, PAUSE, DISCEND, DISCIN or TOCREAD
+		set drive error status to COMMANDERROR and stop processing command
+	if drive status is TOCREAD
+		check request format, if it's TOCT or TOCN, force it to absolute
+		if disc is not validated
+			set drive error status to COMMANDERROR and stop processing command
+	track number is in nibbles 3 and 4 in BCD format
+	calculates PBA of requested track from TOC
+	Sends SEEK command to mechanism for calculated PBA
+	if drive status at start of command was STOP, DISCEND or DISCIN
+		send PLAY command to mechanism
+	emulation state is set to CUING
+command DoorClose
+	if drive status is NOT one of DOOROPEN or TRAYMOVING
+		set drive error status to COMMANDERROR and stop processing command
+	CLOSE command is set to mechanism
+	emulation state is set to CLOSING
+command DoorOpen
+	if drive status is DOOROPEN
+		set drive error status to COMMANDERROR and stop processing command
+	if drive servo is not stopped:
+		set request format to absolute
+	OPEN command is sent to mechanism
+	emulation state set to OPENING
+	disc, TOC and program area validitly flags are cleared
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/notes/notes.txt	Sun Jan 30 22:29:29 2022 -0800
@@ -0,0 +1,76 @@
+cmp.w <ea>, Dn 4(1/0) + <ea> time
+cmp.l <ea>, Dn 6(1/0) + <ea> time
+cmp.w #num, Dn 4(1/0) + 4(1/0)
+cmp.l #num, Dn 6(1/0) + 8(2/0)
+
+cmpi.w #num, Dn 8(2/0)
+cmpi.l #num, Dn 14(3/0)
+
+
+movem
+
+subtype field (bits 9-11) = 110 or 100 depending on direction
+bit 8 = 0
+bit 7 = 1
+bit 6 = size
+
+
+
+x86-64 registers in 68K core
+
+1. native stack pointer
+2. current cycle count
+3. target cycle count
+4. cartridge address
+5. work ram address
+6. scratch register
+7. context pointer (contains 68K registers and memory pointers not in registers)
+8. status register (maybe, depends on how well I can abuse native x86 status stuff)
+Rest of registers used for holding 68K registers
+
+rax = cycle counter
+bl = N flag
+bh = V flag
+rcx = scratch register
+dl = Z flag
+dh = C flag
+rbp = target cycle count
+rsi = context pointer
+rdi = scratch register
+r8 = cartridge address
+r9 = work ram address
+r10 = d0
+r11 = d1
+r12 = d2
+r13 = a0
+r14 = a1
+r15 = a7
+rsp = native stack pointer
+
+68K context:
+uint8_t flags[5];
+uint8_t pad??[3]
+uint32_t dregs[8]; //8 + 4 * reg
+uint32_t aregs[8]; //40 + 4 * reg
+.....
+
+x86-64 registers in Z80 core
+
+ax = HL
+bx = BC
+cx = DE
+dx = IX
+ebp = current cycle count
+rsi = context pointer
+edi = target cycle count
+rsp = native stack pointer
+r8 = IY
+r9 = SP
+r10 = A (maybe AF?)
+r11 = z80 ram address
+r12 = cartridge address if bank is pointed at ROM
+r13 = scratch1
+r14 = scratch2
+r15 = ?maybe z80 bank register?
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/notes/sf2_50mhz_analysis.txt	Sun Jan 30 22:29:29 2022 -0800
@@ -0,0 +1,173 @@
+HSYNC End
+Plane A: D13C
+??slot??
+tile
+tile
+Plane B: F29C
+?sprite tile slot?
+tile
+tile
+Plane A: D100
+68K Slot
+tile
+tile
+Plane B: F2A0
+?sprite table slot?
+tile
+tile
+Plane A: D104
+68K slot
+tile
+tile
+Plane B: F2A4
+?sprite table slot?
+tile
+tile
+Plane A: D108
+68K slot
+?tile?
+?tile?
+Plane B: F2A8
+?sprite table slot?
+tile
+tile
+Plane A: D10C
+refresh
+?tile?
+?tile?
+Plane B: F22C
+?sprite table slot?
+tile
+tile
+Plane A: D110
+68K slot
+?tile?
+?tile?
+Plane B: F230
+?sprite table slot?
+tile
+tile
+Plane A: D114
+68K slot
+?tile?
+?tile?
+Plane B: F2F4
+?sprite table slot?
+tile
+tile
+Plane A: D118
+68K slot
+?tile?
+?tile?
+Plane B: F238
+?sprite table slot?
+ttile tile
+Plane A: D11C
+refresh
+?tile?
+?tile?
+Plane B: F23C
+?sprite table slot?
+tile
+tile
+Plane A: D120
+68K slot
+?tile?
+?tile?
+Plane B: F240
+?sprite table slot?
+tile
+tile
+Plane A: D124
+68K Slot
+?tile?
+?tile?
+Plane B: F244
+?sprite table slot?
+tile
+tile
+Plane A: D128
+68K Slot
+?tile?
+?tile?
+Plane B: F3C8
+?sprite table slot?
+tile
+tile
+Plane A: D12C
+refresh
+?tile?
+?tile?
+Plane B: F24C
+?sprite table slot?
+tile
+tile
+Plane A: D130
+68K slot
+?tile?
+?tile?
+Plane B: F250
+?sprite table slot?
+tile
+tile
+Plane A: D134
+68K Slot
+?tile?
+?tile?
+Plane B: F254
+?sprite table slot?
+tile
+tile
+Plane A: D138
+68K slot
+?tile?
+?tile?
+Plane B: F3D8
+?sprite table slot?
+tile
+tile
+Plane A: D13C
+refresh
+tile
+tile
+Plane B: F3DC
+?sprite table slot?
+tile
+tile
+---------------
+68K Slot
+68K Slot
+sprite tile slot
+sprite tile slot
+sprite tile slot
+sprite tile slot
+?sprite tile slot?
+?sprite tile slot?
+?sprite tile slot?
+?sprite tile slot?
+?sprite tile slot?
+?sprite tile slot?
+?sprite tile slot?
+?sprite tile slot?
+?sprite tile slot?
+?sprite tile slot?
+68K Slot
+?sprite tile slot?
+?sprite tile slot?
+?sprite tile slot?
+?sprite tile slot?
+?sprite tile slot?
+HSYNC Start
+?sprite tile slot?
+?sprite tile slot?
+?sprite tile slot?
+?sprite tile slot?
+?sprite tile slot?
+?sprite tile slot?
+?unkown tile slot?
+68K Slot
+Horizontal Scroll
+?sprite tile slot?
+?sprite tile slot?
+?sprite tile slot?
+?unkown tile slot?
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/notes/sf2_vram_map.txt	Sun Jan 30 22:29:29 2022 -0800
@@ -0,0 +1,64 @@
+0000: single color blocks
+0200: Random symbols????
+0400-0C00: Font
+0C00-0E??: Score font
+0E??: Player name displays
+
+2000-3000: floor (mostly, some garbage around 2800 and 2A00)
+3000-6C00: background
+6C00-8000: not graphics
+8000-~8A00: bat/bird sprites, special attack text
+8A00-9000: ?????
+9000-9200: More special attack text
+9200-9600: another font
+9800-AA00: special attack effects?
+AC00-B000: ????
+B000-B500: character sprite
+????
+C000-C500: chracter sprite
+D000-DA00: window name table
+DA00-DC00: Sprite attribute table
+DC00-E000: horizontal scroll data
+E000-????: plane A&B name table
+C500-FFFF: not graphics
+
+VDP Registers:
+Mode
+00 - 14
+01 - 64
+Scroll A Name Table Address: E000
+02 - 38
+Window Name Table Address: D000
+03 - 34
+Scroll B Name Table Address: E000
+04 - 07
+Sprite Attribute Table Address: DA00
+05 - 6D
+06 - 00
+Backdrop color - 0
+07 - 00
+08 - 00
+09 - 00
+HINT Counter
+0A - AF
+Mode - Full screen vertical scroll, line horizontal scroll, external ints disabled, 32 cell display, no interlacing
+0B - 03
+0C - 00
+H Scroll Data Address: DC00
+0D - 37
+0E - 00
+Auto increment
+0F - 02
+Scroll Size
+10 - 11
+Window H Pos
+11 - 00
+Window V Pos
+12 - 05
+DMA transfer length
+13 - 00
+14 - 00
+DMA source address and mode
+15 - DB
+16 - CF
+17 - 7F
--- a/romdb.c	Sat Jan 01 18:54:46 2022 -0800
+++ b/romdb.c	Sun Jan 30 22:29:29 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;
 }
@@ -472,6 +475,19 @@
 	info.rom_size = rom_size;
 	add_memmap_header(&info, rom, rom_size, base_map, base_chunks);
 	info.port1_override = info.port2_override = info.ext_override = info.mouse_mode = NULL;
+	info.wants_cd = 0;
+	for (uint32_t offset = 0x190; offset < rom_size && offset < 0x1A0; offset++)
+	{
+		if (rom[offset] == 'F') {
+			// probably a codemasters game with a garbage header
+			break;
+		}
+		if (rom[offset] == 'C') {
+			info.wants_cd = 1;
+			break;
+		}
+	}
+
 	return info;
 }
 
@@ -1052,6 +1068,7 @@
 		info.port1_override = info.port2_override = info.ext_override = NULL;
 	}
 	info.mouse_mode = tern_find_ptr(entry, "mouse_mode");
+	info.wants_cd = !strcmp(tern_find_ptr_default(entry, "wants_cd", "no"), "yes");
 
 	return info;
 }
--- a/romdb.h	Sat Jan 01 18:54:46 2022 -0800
+++ b/romdb.h	Sun Jan 30 22:29:29 2022 -0800
@@ -79,12 +79,13 @@
 	uint8_t       mapper_type;
 	uint8_t       regions;
 	uint8_t       is_save_lock_on; //Does the save buffer actually belong to a lock-on cart?
+	uint8_t       wants_cd;
 };
 
 #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	Sun Jan 30 22:29:29 2022 -0800
@@ -0,0 +1,1208 @@
+#include <stdlib.h>
+#include <string.h>
+#include "cd_graphics.h"
+#include "genesis.h"
+#include "util.h"
+
+#define SCD_MCLKS 50000000
+#define SCD_PERIPH_RESET_CLKS (SCD_MCLKS / 10)
+#define TIMER_TICK_CLKS 1536
+
+enum {
+	GA_SUB_CPU_CTRL,
+	GA_MEM_MODE,
+	GA_CDC_CTRL,
+	GA_CDC_REG_DATA,
+	GA_CDC_HOST_DATA,
+	GA_CDC_DMA_ADDR,
+	GA_STOP_WATCH,
+	GA_COMM_FLAG,
+	GA_COMM_CMD0,
+	GA_COMM_CMD1,
+	GA_COMM_CMD2,
+	GA_COMM_CMD3,
+	GA_COMM_CMD4,
+	GA_COMM_CMD5,
+	GA_COMM_CMD6,
+	GA_COMM_CMD7,
+	GA_COMM_STATUS0,
+	GA_COMM_STATUS1,
+	GA_COMM_STATUS2,
+	GA_COMM_STATUS3,
+	GA_COMM_STATUS4,
+	GA_COMM_STATUS5,
+	GA_COMM_STATUS6,
+	GA_COMM_STATUS7,
+	GA_TIMER,
+	GA_INT_MASK,
+	GA_CDD_FADER,
+	GA_CDD_CTRL,
+	GA_CDD_STATUS0,
+	GA_CDD_STATUS1,
+	GA_CDD_STATUS2,
+	GA_CDD_STATUS3,
+	GA_CDD_STATUS4,
+	GA_CDD_CMD0,
+	GA_CDD_CMD1,
+	GA_CDD_CMD2,
+	GA_CDD_CMD3,
+	GA_CDD_CMD4,
+	GA_FONT_COLOR,
+	GA_FONT_BITS,
+	GA_FONT_DATA0,
+	GA_FONT_DATA1,
+	GA_FONT_DATA2,
+	GA_FONT_DATA3,
+
+	GA_HINT_VECTOR = GA_CDC_REG_DATA
+};
+//GA_SUB_CPU_CTRL
+#define BIT_IEN2       0x8000
+#define BIT_IFL2       0x0100
+#define BIT_LEDG       0x0200
+#define BIT_LEDR       0x0100
+#define BIT_SBRQ       0x0002
+#define BIT_SRES       0x0001
+#define BIT_PRES       0x0001
+//GA_MEM_MODE
+#define MASK_PROG_BANK 0x00C0
+#define BIT_OVERWRITE  0x0010
+#define BIT_UNDERWRITE 0x0008
+#define MASK_PRIORITY  (BIT_OVERWRITE|BIT_UNDERWRITE)
+#define BIT_MEM_MODE   0x0004
+#define BIT_DMNA       0x0002
+#define BIT_RET        0x0001
+
+//GA_CDC_CTRL
+#define BIT_EDT        0x8000
+#define BIT_DSR        0x4000
+
+enum {
+	DST_MAIN_CPU = 2,
+	DST_SUB_CPU,
+	DST_PCM_RAM,
+	DST_PROG_RAM,
+	DST_WORD_RAM = 7
+};
+
+//GA_INT_MASK
+#define BIT_MASK_IEN1  0x0002
+#define BIT_MASK_IEN2  0x0004
+#define BIT_MASK_IEN3  0x0008
+#define BIT_MASK_IEN4  0x0010
+#define BIT_MASK_IEN5  0x0020
+#define BIT_MASK_IEN6  0x0040
+
+//GA_CDD_CTRL
+#define BIT_HOCK       0x0004
+
+static void *prog_ram_wp_write16(uint32_t address, void *vcontext, uint16_t value)
+{
+	m68k_context *m68k = vcontext;
+	segacd_context *cd = m68k->system;
+	//if (!(cd->gate_array[GA_MEM_MODE] & (1 << ((address >> 9) + 8)))) {
+	if (address >= ((cd->gate_array[GA_MEM_MODE] & 0xFF00) << 1)) {
+		cd->prog_ram[address >> 1] = value;
+		m68k_invalidate_code_range(m68k, address, address + 2);
+	}
+	return vcontext;
+}
+
+static void *prog_ram_wp_write8(uint32_t address, void *vcontext, uint8_t value)
+{
+	m68k_context *m68k = vcontext;
+	segacd_context *cd = m68k->system;
+	if (address >= ((cd->gate_array[GA_MEM_MODE] & 0xFF00) << 1)) {
+		((uint8_t *)cd->prog_ram)[address ^ 1] = value;
+		m68k_invalidate_code_range(m68k, address, address + 1);
+	}
+	return vcontext;
+}
+
+static uint16_t word_ram_2M_read16(uint32_t address, void *vcontext)
+{
+	m68k_context *m68k = vcontext;
+	//TODO: fixme for interleaving
+	uint16_t* bank = m68k->mem_pointers[1];
+	uint16_t raw = bank[address >> 2];
+	if (address & 2) {
+		return (raw & 0xF) | (raw << 4 & 0xF00);
+	} else {
+		return (raw >> 4 & 0xF00) | (raw >> 8 & 0xF);
+	}
+}
+
+static uint8_t word_ram_2M_read8(uint32_t address, void *vcontext)
+{
+	uint16_t word = word_ram_2M_read16(address, vcontext);
+	if (address & 1) {
+		return word;
+	}
+	return word >> 8;
+}
+
+static void *word_ram_2M_write8(uint32_t address, void *vcontext, uint8_t value)
+{
+	m68k_context *m68k = vcontext;
+	segacd_context *cd = m68k->system;
+	value &= 0xF;
+	uint16_t priority = cd->gate_array[GA_MEM_MODE] & MASK_PRIORITY;
+
+	if (priority == BIT_OVERWRITE && !value) {
+		return vcontext;
+	}
+	if (priority == BIT_UNDERWRITE) {
+		if (!value) {
+			return vcontext;
+		}
+		uint8_t old = word_ram_2M_read8(address, vcontext);
+		if (old) {
+			return vcontext;
+		}
+	}
+	uint16_t* bank = m68k->mem_pointers[1];
+	uint16_t raw = bank[address >> 2];
+	uint16_t shift = ((address & 3) * 4);
+	raw &= ~(0xF000 >> shift);
+	raw |= value << (12 - shift);
+	bank[address >> 2] = raw;
+	return vcontext;
+}
+
+
+static void *word_ram_2M_write16(uint32_t address, void *vcontext, uint16_t value)
+{
+	word_ram_2M_write8(address, vcontext, value >> 8);
+	return word_ram_2M_write8(address + 1, vcontext, value);
+}
+
+static uint16_t word_ram_1M_read16(uint32_t address, void *vcontext)
+{
+	return 0;
+}
+
+static uint8_t word_ram_1M_read8(uint32_t address, void *vcontext)
+{
+	return 0;
+}
+
+static void *word_ram_1M_write16(uint32_t address, void *vcontext, uint16_t value)
+{
+	return vcontext;
+}
+
+static void *word_ram_1M_write8(uint32_t address, void *vcontext, uint8_t value)
+{
+	return vcontext;
+}
+
+
+static uint16_t unmapped_prog_read16(uint32_t address, void *vcontext)
+{
+	return 0xFFFF;
+}
+
+static uint8_t unmapped_prog_read8(uint32_t address, void *vcontext)
+{
+	return 0xFF;
+}
+
+static void *unmapped_prog_write16(uint32_t address, void *vcontext, uint16_t value)
+{
+	return vcontext;
+}
+
+static void *unmapped_prog_write8(uint32_t address, void *vcontext, uint8_t value)
+{
+	return vcontext;
+}
+
+static uint16_t unmapped_word_read16(uint32_t address, void *vcontext)
+{
+	return 0xFFFF;
+}
+
+static uint8_t unmapped_word_read8(uint32_t address, void *vcontext)
+{
+	return 0xFF;
+}
+
+static void *unmapped_word_write16(uint32_t address, void *vcontext, uint16_t value)
+{
+	return vcontext;
+}
+
+static void *unmapped_word_write8(uint32_t address, void *vcontext, uint8_t value)
+{
+	return vcontext;
+}
+
+static uint16_t cell_image_read16(uint32_t address, void *vcontext)
+{
+	return 0xFFFF;
+}
+
+static uint8_t cell_image_read8(uint32_t address, void *vcontext)
+{
+	return 0xFF;
+}
+
+static void *cell_image_write16(uint32_t address, void *vcontext, uint16_t value)
+{
+	return vcontext;
+}
+
+static void *cell_image_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 void timers_run(segacd_context *cd, uint32_t cycle)
+{
+	if (cycle <= cd->stopwatch_cycle) {
+		return;
+	}
+	uint32_t ticks = (cycle - cd->stopwatch_cycle) / TIMER_TICK_CLKS;
+	cd->stopwatch_cycle += ticks * TIMER_TICK_CLKS;
+	cd->gate_array[GA_STOP_WATCH] += ticks;
+	cd->gate_array[GA_STOP_WATCH] &= 0xFFF;
+	if (ticks && !cd->timer_value) {
+		--ticks;
+		cd->timer_value = cd->gate_array[GA_TIMER];
+	}
+	if (ticks && cd->timer_value) {
+		while (ticks >= (cd->timer_value + 1)) {
+			ticks -= cd->timer_value + 1;
+			cd->timer_value = cd->gate_array[GA_TIMER];
+			cd->timer_pending = 1;
+		}
+		cd->timer_value -= ticks;
+		if (!cd->timer_value) {
+			cd->timer_pending = 1;
+		}
+	}
+}
+
+static void cdd_run(segacd_context *cd, uint32_t cycle)
+{
+	cdd_mcu_run(&cd->cdd, cycle, cd->gate_array + GA_CDD_CTRL, &cd->cdc);
+	lc8951_run(&cd->cdc, cycle);
+}
+
+static uint32_t next_timer_int(segacd_context *cd)
+{
+	if (cd->timer_pending) {
+		return cd->stopwatch_cycle;
+	}
+	if (cd->timer_value) {
+		return cd->stopwatch_cycle + TIMER_TICK_CLKS * cd->timer_value;
+	}
+	if (cd->gate_array[GA_TIMER]) {
+		return cd->stopwatch_cycle + TIMER_TICK_CLKS * (cd->gate_array[GA_TIMER] + 1);
+	}
+	return CYCLE_NEVER;
+}
+
+static void calculate_target_cycle(m68k_context * context)
+{
+	segacd_context *cd = context->system;
+	context->int_cycle = CYCLE_NEVER;
+	uint8_t mask = context->status & 0x7;
+	if (mask < 5) {
+		if (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN5) {
+			uint32_t cdc_cycle = lc8951_next_interrupt(&cd->cdc);
+			//CDC interrupts only generated on falling edge of !INT signal
+			if (cd->cdc_int_ack) {
+				if (cdc_cycle > cd->cdc.cycle) {
+					cd->cdc_int_ack = 0;
+				} else {
+					cdc_cycle = CYCLE_NEVER;
+				}
+			}
+			if (cdc_cycle < context->int_cycle) {
+				context->int_cycle = cdc_cycle;
+				context->int_num = 5;
+			}
+		}
+		if (mask < 4) {
+			if (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN4) {
+				uint32_t cdd_cycle = cd->cdd.int_pending ? context->current_cycle : cd->cdd.next_int_cycle;
+				if (cdd_cycle < context->int_cycle) {
+					context->int_cycle = cdd_cycle;
+					context->int_num = 4;
+				}
+			}
+			if (mask < 3) {
+				uint32_t next_timer;
+				if (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN3) {
+					uint32_t next_timer_cycle = next_timer_int(cd);
+					if (next_timer_cycle < context->int_cycle) {
+						context->int_cycle = next_timer_cycle;
+						context->int_num = 3;
+					}
+				}
+				if (mask < 2) {
+					if (cd->int2_cycle < context->int_cycle && (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN2)) {
+						context->int_cycle = cd->int2_cycle;
+						context->int_num = 2;
+					}
+					if (mask < 1) {
+						if (cd->graphics_int_cycle < context->int_cycle && (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN1)) {
+							context->int_cycle = cd->graphics_int_cycle;
+							context->int_num = 1;
+						}
+					}
+				}
+			}
+		}
+	}
+	if (context->int_cycle > context->current_cycle && context->int_pending == INT_PENDING_SR_CHANGE) {
+		context->int_pending = INT_PENDING_NONE;
+	}
+	if (context->current_cycle >= context->sync_cycle) {
+		context->should_return = 1;
+		context->target_cycle = context->current_cycle;
+		return;
+	}
+	if (context->status & M68K_STATUS_TRACE || context->trace_pending) {
+		context->target_cycle = context->current_cycle;
+		return;
+	}
+	context->target_cycle = context->sync_cycle < context->int_cycle ? context->sync_cycle : context->int_cycle;
+}
+
+static uint16_t sub_gate_read16(uint32_t address, void *vcontext)
+{
+	m68k_context *m68k = vcontext;
+	segacd_context *cd = m68k->system;
+	uint32_t reg = address >> 1;
+	switch (reg)
+	{
+	case GA_SUB_CPU_CTRL: {
+		uint16_t value = cd->gate_array[reg] & 0xFFFE;
+		if (cd->periph_reset_cycle == CYCLE_NEVER || (m68k->current_cycle - cd->periph_reset_cycle) > SCD_PERIPH_RESET_CLKS) {
+			value |= BIT_PRES;
+		}
+		return value;
+	}
+	case GA_MEM_MODE:
+		return cd->gate_array[reg] & 0xFF1F;
+	case GA_CDC_CTRL:
+		cdd_run(cd, m68k->current_cycle);
+		return cd->gate_array[reg] | cd->cdc.ar;
+	case GA_CDC_REG_DATA:
+		cdd_run(cd, m68k->current_cycle);
+		return lc8951_reg_read(&cd->cdc);
+	case GA_CDC_HOST_DATA: {
+		cdd_run(cd, m68k->current_cycle);
+		uint16_t dst = cd->gate_array[GA_CDC_CTRL] >> 8 & 0x7;
+		if (dst == DST_SUB_CPU) {
+			if (cd->gate_array[GA_CDC_CTRL] & BIT_DSR) {
+				cd->gate_array[GA_CDC_CTRL] &= ~BIT_DSR;
+				lc8951_resume_transfer(&cd->cdc, cd->cdc.cycle);
+			}
+			calculate_target_cycle(cd->m68k);
+
+		}
+		return cd->gate_array[reg];
+	}
+	case GA_STOP_WATCH:
+	case GA_TIMER:
+		timers_run(cd, m68k->current_cycle);
+		return cd->gate_array[reg];
+	case GA_CDD_STATUS0:
+	case GA_CDD_STATUS1:
+	case GA_CDD_STATUS2:
+	case GA_CDD_STATUS3:
+	case GA_CDD_STATUS4:
+		cdd_run(cd, m68k->current_cycle);
+		return cd->gate_array[reg];
+		break;
+	case GA_FONT_DATA0:
+	case GA_FONT_DATA1:
+	case GA_FONT_DATA2:
+	case GA_FONT_DATA3: {
+		uint16_t shift = 4 * (3 - (reg - GA_FONT_DATA0));
+		uint16_t value = 0;
+		uint16_t fg = cd->gate_array[GA_FONT_COLOR] >> 4;
+		uint16_t bg = cd->gate_array[GA_FONT_COLOR] & 0xF;
+		for (int i = 0; i < 4; i++) {
+			uint16_t pixel = 0;
+			if (cd->gate_array[GA_FONT_BITS] & 1 << (shift + i)) {
+				pixel = fg;
+			} else {
+				pixel = bg;
+			}
+			value |= pixel << (i * 4);
+		}
+		return value;
+	case GA_STAMP_SIZE:
+	case GA_IMAGE_BUFFER_LINES:
+		//these two have bits that change based on graphics operations
+		cd_graphics_run(cd, m68k->current_cycle);
+		return cd->gate_array[reg];
+	case GA_TRACE_VECTOR_BASE:
+		//write only
+		return 0xFFFF;
+	}
+	default:
+		return cd->gate_array[reg];
+	}
+}
+
+static uint8_t sub_gate_read8(uint32_t address, void *vcontext)
+{
+	uint16_t val = sub_gate_read16(address, vcontext);
+	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 >> 1;
+	switch (reg)
+	{
+	case GA_SUB_CPU_CTRL:
+		cd->gate_array[reg] &= 0xF0;
+		cd->gate_array[reg] |= value & (BIT_LEDG|BIT_LEDR);
+		if (value & BIT_PRES) {
+			cd->periph_reset_cycle = m68k->current_cycle;
+		}
+		break;
+	case GA_MEM_MODE: {
+		uint16_t changed = value ^ cd->gate_array[reg];
+		genesis_context *gen = cd->genesis;
+		if (changed & BIT_MEM_MODE) {
+			//FIXME: ram banks are supposed to be interleaved when in 2M mode
+			cd->gate_array[reg] &= ~BIT_DMNA;
+			if (value & BIT_MEM_MODE) {
+				//switch to 1M mode
+				gen->m68k->mem_pointers[cd->memptr_start_index + 1] = (value & BIT_RET) ? cd->word_ram + 0x10000 : cd->word_ram;
+				gen->m68k->mem_pointers[cd->memptr_start_index + 2] = NULL;
+				m68k->mem_pointers[0] = NULL;
+				m68k->mem_pointers[1] = (value & BIT_RET) ? cd->word_ram : cd->word_ram + 0x10000;
+			} else {
+				//switch to 2M mode
+				if (value & BIT_RET) {
+					//Main CPU will have word ram
+					gen->m68k->mem_pointers[cd->memptr_start_index + 1] = cd->word_ram;
+					gen->m68k->mem_pointers[cd->memptr_start_index + 2] = cd->word_ram + 0x10000;
+					m68k->mem_pointers[0] = NULL;
+					m68k->mem_pointers[1] = NULL;
+				} else {
+					//sub cpu will have word ram
+					gen->m68k->mem_pointers[cd->memptr_start_index + 1] = NULL;
+					gen->m68k->mem_pointers[cd->memptr_start_index + 2] = NULL;
+					m68k->mem_pointers[0] = cd->word_ram;
+					m68k->mem_pointers[1] = NULL;
+				}
+			}
+			m68k_invalidate_code_range(gen->m68k, cd->base + 0x200000, cd->base + 0x240000);
+			m68k_invalidate_code_range(m68k, 0x080000, 0x0E0000);
+		} else if (changed & BIT_RET) {
+			if (value & BIT_MEM_MODE) {
+				cd->gate_array[reg] &= ~BIT_DMNA;
+				//swapping banks in 1M mode
+				gen->m68k->mem_pointers[cd->memptr_start_index + 1] = (value & BIT_RET) ? cd->word_ram + 0x10000 : cd->word_ram;
+				m68k->mem_pointers[1] = (value & BIT_RET) ? cd->word_ram : cd->word_ram + 0x10000;
+				m68k_invalidate_code_range(gen->m68k, cd->base + 0x200000, cd->base + 0x240000);
+				m68k_invalidate_code_range(m68k, 0x080000, 0x0E0000);
+			} else if (value & BIT_RET) {
+				cd->gate_array[reg] &= ~BIT_DMNA;
+				//giving word ram to main CPU in 2M mode
+				gen->m68k->mem_pointers[cd->memptr_start_index + 1] = cd->word_ram;
+				gen->m68k->mem_pointers[cd->memptr_start_index + 2] = cd->word_ram + 0x10000;
+				m68k->mem_pointers[0] = NULL;
+				m68k_invalidate_code_range(gen->m68k, cd->base + 0x200000, cd->base + 0x240000);
+				m68k_invalidate_code_range(m68k, 0x080000, 0x0E0000);
+			} else {
+				value |= BIT_RET;
+			}
+		}
+		cd->gate_array[reg] &= 0xFFC2;
+		cd->gate_array[reg] |= value & (BIT_RET|BIT_MEM_MODE|MASK_PRIORITY);
+		break;
+	}
+	case GA_CDC_CTRL:
+		cdd_run(cd, m68k->current_cycle);
+		lc8951_ar_write(&cd->cdc, value);
+		//cd->gate_array[reg] &= 0xC000;
+		//apparently this clears EDT, should it also clear DSR?
+		cd->gate_array[reg] = value & 0x0700;
+		cd->cdc_dst_low = 0;
+		break;
+	case GA_CDC_REG_DATA:
+		cdd_run(cd, m68k->current_cycle);
+		printf("CDC write %X: %X @ %u\n", cd->cdc.ar, value, m68k->current_cycle);
+		lc8951_reg_write(&cd->cdc, value);
+		calculate_target_cycle(m68k);
+		break;
+	case GA_CDC_DMA_ADDR:
+		cdd_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value;
+		cd->cdc_dst_low = 0;
+		break;
+	case GA_STOP_WATCH:
+		//docs say you should only write zero to reset
+		//mcd-verificator comments suggest any value will reset
+		timers_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = 0;
+		break;
+	case GA_COMM_FLAG:
+		cd->gate_array[reg] &= 0xFF00;
+		cd->gate_array[reg] |= value & 0xFF;
+		break;
+	case GA_COMM_STATUS0:
+	case GA_COMM_STATUS1:
+	case GA_COMM_STATUS2:
+	case GA_COMM_STATUS3:
+	case GA_COMM_STATUS4:
+	case GA_COMM_STATUS5:
+	case GA_COMM_STATUS6:
+	case GA_COMM_STATUS7:
+		//no effects for these other than saving the value
+		cd->gate_array[reg] = value;
+		break;
+	case GA_TIMER:
+		timers_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value & 0xFF;
+		calculate_target_cycle(m68k);
+		break;
+	case GA_INT_MASK:
+		cd->gate_array[reg] = value & (BIT_MASK_IEN6|BIT_MASK_IEN5|BIT_MASK_IEN4|BIT_MASK_IEN3|BIT_MASK_IEN2|BIT_MASK_IEN1);
+		calculate_target_cycle(m68k);
+		break;
+	case GA_CDD_CTRL: {
+		cdd_run(cd, m68k->current_cycle);
+		uint16_t changed = cd->gate_array[reg] ^ value;
+		cd->gate_array[reg] &= ~BIT_HOCK;
+		cd->gate_array[reg] |= value & BIT_HOCK;
+		if (changed & BIT_HOCK) {
+			if (value & BIT_HOCK) {
+				cdd_hock_enabled(&cd->cdd);
+			} else {
+				cdd_hock_disabled(&cd->cdd);
+			}
+			calculate_target_cycle(m68k);
+		}
+		break;
+	}
+	case GA_CDD_CMD0:
+	case GA_CDD_CMD1:
+	case GA_CDD_CMD2:
+	case GA_CDD_CMD3:
+		cdd_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value & 0x0F0F;
+		break;
+	case GA_CDD_CMD4:
+		cdd_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value & 0x0F0F;
+		cdd_mcu_start_cmd_recv(&cd->cdd, cd->gate_array + GA_CDD_CTRL);
+		break;
+	case GA_FONT_COLOR:
+		cd->gate_array[reg] = value & 0xFF;
+		break;
+	case GA_FONT_BITS:
+		cd->gate_array[reg] = value;
+		break;
+	case GA_STAMP_SIZE:
+		cd_graphics_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] &= BIT_GRON;
+		cd->gate_array[reg] |= value & (BIT_SMS|BIT_STS|BIT_RPT);
+		break;
+	case GA_STAMP_MAP_BASE:
+		cd_graphics_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value & 0xFFE0;
+		break;
+	case GA_IMAGE_BUFFER_VCELLS:
+		cd_graphics_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value & 0x1F;
+		break;
+	case GA_IMAGE_BUFFER_START:
+		cd_graphics_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value & 0xFFF8;
+		break;
+	case GA_IMAGE_BUFFER_OFFSET:
+		cd_graphics_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value & 0x3F;
+		break;
+	case GA_IMAGE_BUFFER_HDOTS:
+		cd_graphics_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value & 0x1FF;
+		break;
+	case GA_IMAGE_BUFFER_LINES:
+		cd_graphics_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value & 0xFF;
+		break;
+	case GA_TRACE_VECTOR_BASE:
+		cd_graphics_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value & 0xFFFE;
+		cd_graphics_start(cd);
+		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;
+	switch (address >> 1)
+	{
+	case GA_CDC_HOST_DATA:
+	case GA_CDC_DMA_ADDR:
+	case GA_STOP_WATCH:
+	case GA_COMM_FLAG:
+	case GA_TIMER:
+	case GA_CDD_FADER:
+	case GA_FONT_COLOR:
+		//these registers treat all writes as word-wide
+		value16 = value | (value << 8);
+		break;
+	case GA_CDC_CTRL:
+		if (address & 1) {
+			lc8951_ar_write(&cd->cdc, value);
+			return vcontext;
+		} else {
+			value16 = cd->cdc.ar | (value << 8);
+		}
+		break;
+	case GA_CDD_CMD4:
+		if (!address) {
+			//byte write to $FF804A should not trigger transfer
+			cdd_run(cd, m68k->current_cycle);
+			cd->gate_array[reg] &= 0x0F;
+			cd->gate_array[reg] |= (value << 8 & 0x0F00);
+			return vcontext;
+		}
+		//intentional fallthrough for $FF804B
+	default:
+		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 uint8_t handle_cdc_byte(void *vsys, uint8_t value)
+{
+	segacd_context *cd = vsys;
+	if (cd->gate_array[GA_CDC_CTRL] & BIT_DSR) {
+		//host reg is already full, pause transfer
+		return 0;
+	}
+	if (cd->cdc.cycle == cd->cdc.transfer_end) {
+		cd->gate_array[GA_CDC_CTRL] |= BIT_EDT;
+		printf("EDT set at %u\n", cd->cdc.cycle);
+	}
+	uint16_t dest = cd->gate_array[GA_CDC_CTRL] >> 8 & 0x7;
+	if (!(cd->cdc_dst_low & 1)) {
+		cd->gate_array[GA_CDC_HOST_DATA] &= 0xFF;
+		cd->gate_array[GA_CDC_HOST_DATA] |= value << 8;
+		cd->cdc_dst_low++;
+		if (dest != DST_PCM_RAM) {
+			//PCM RAM writes a byte at a time
+			return 1;
+		}
+	} else {
+		cd->gate_array[GA_CDC_HOST_DATA] &= 0xFF00;
+		cd->gate_array[GA_CDC_HOST_DATA] |= value;
+	}
+
+	uint32_t dma_addr = cd->gate_array[GA_CDC_DMA_ADDR] << 3;
+	dma_addr |= cd->cdc_dst_low;
+	switch (dest)
+	{
+	case DST_MAIN_CPU:
+	case DST_SUB_CPU:
+		cd->cdc_dst_low = 0;
+		cd->gate_array[GA_CDC_CTRL] |= BIT_DSR;
+		printf("DSR set at %u, (transfer_end %u, dbcl %X, dbch %X)\n", cd->cdc.cycle, cd->cdc.transfer_end, cd->cdc.regs[2], cd->cdc.regs[3]);
+		break;
+	case DST_PCM_RAM:
+		dma_addr &= (1 << 13) - 1;
+		//TODO: write to currently visible 8K bank of PCM RAM I guess?
+		dma_addr += 2;
+		cd->cdc_dst_low = dma_addr & 7;
+		cd->gate_array[GA_CDC_DMA_ADDR] = dma_addr >> 3;
+		break;
+	case DST_PROG_RAM:
+		cd->prog_ram[dma_addr >> 1] = cd->gate_array[GA_CDC_HOST_DATA];
+		m68k_invalidate_code_range(cd->m68k, dma_addr - 1, dma_addr + 1);
+		dma_addr++;
+		cd->cdc_dst_low = dma_addr & 7;
+		cd->gate_array[GA_CDC_DMA_ADDR] = dma_addr >> 3;
+		break;
+	case DST_WORD_RAM:
+		if (cd->gate_array[GA_MEM_MODE] & BIT_MEM_MODE) {
+			//1M mode, write to bank assigned to Sub CPU
+			dma_addr &= (1 << 17) - 1;
+			cd->m68k->mem_pointers[1][dma_addr >> 1] = cd->gate_array[GA_CDC_HOST_DATA];
+			m68k_invalidate_code_range(cd->m68k, 0x0C0000 + dma_addr - 1, 0x0C0000 + dma_addr + 1);
+		} else {
+			//2M mode, check if Sub CPU has access
+			if (!(cd->gate_array[GA_MEM_MODE] & BIT_RET)) {
+				dma_addr &= (1 << 18) - 1;
+				cd->word_ram[dma_addr >> 1] = cd->gate_array[GA_CDC_HOST_DATA];
+				m68k_invalidate_code_range(cd->m68k, 0x080000 + dma_addr, 0x080000 + dma_addr + 1);
+			}
+		}
+		dma_addr++;
+		cd->cdc_dst_low = dma_addr & 7;
+		cd->gate_array[GA_CDC_DMA_ADDR] = dma_addr >> 3;
+		break;
+	default:
+		printf("Invalid CDC transfer destination %d\n", dest);
+	}
+	return 1;
+}
+
+static uint8_t can_main_access_prog(segacd_context *cd)
+{
+	//TODO: use actual busack
+	return cd->busreq || !cd->reset;
+}
+
+static void scd_peripherals_run(segacd_context *cd, uint32_t cycle)
+{
+	timers_run(cd, cycle);
+	cdd_run(cd, cycle);
+	cd_graphics_run(cd, cycle);
+}
+
+static m68k_context *sync_components(m68k_context * context, uint32_t address)
+{
+	segacd_context *cd = context->system;
+	scd_peripherals_run(cd, context->current_cycle);
+	switch (context->int_ack)
+	{
+	case 1:
+		cd->graphics_int_cycle = CYCLE_NEVER;
+		break;
+	case 2:
+		cd->int2_cycle = CYCLE_NEVER;
+		break;
+	case 3:
+		cd->timer_pending = 0;
+		break;
+	case 4:
+		cd->cdd.int_pending = 0;
+		break;
+	case 5:
+		cd->cdc_int_ack = 1;
+		break;
+	}
+	context->int_ack = 0;
+	calculate_target_cycle(context);
+	return context;
+}
+
+void scd_run(segacd_context *cd, uint32_t cycle)
+{
+	uint8_t m68k_run = !can_main_access_prog(cd);
+	if (cycle > cd->m68k->current_cycle) {
+		if (m68k_run) {
+			uint32_t start = cd->m68k->current_cycle;
+			cd->m68k->sync_cycle = cycle;
+			if (cd->need_reset) {
+				cd->need_reset = 0;
+				m68k_reset(cd->m68k);
+			} else {
+				calculate_target_cycle(cd->m68k);
+				resume_68k(cd->m68k);
+			}
+		} else {
+			cd->m68k->current_cycle = cycle;
+		}
+	}
+	scd_peripherals_run(cd, cycle);
+}
+
+uint32_t gen_cycle_to_scd(uint32_t cycle, genesis_context *gen)
+{
+	return ((uint64_t)cycle) * ((uint64_t)SCD_MCLKS) / ((uint64_t)gen->normal_clock);
+}
+
+void scd_adjust_cycle(segacd_context *cd, uint32_t deduction)
+{
+	deduction = gen_cycle_to_scd(deduction, cd->genesis);
+	cd->m68k->current_cycle -= deduction;
+	cd->stopwatch_cycle -= deduction;
+	if (deduction >= cd->int2_cycle) {
+		cd->int2_cycle = 0;
+	} else if (cd->int2_cycle != CYCLE_NEVER) {
+		cd->int2_cycle -= deduction;
+	}
+	if (deduction >= cd->periph_reset_cycle) {
+		cd->periph_reset_cycle = CYCLE_NEVER;
+	} else if (cd->periph_reset_cycle != CYCLE_NEVER) {
+		cd->periph_reset_cycle -= deduction;
+	}
+	cdd_mcu_adjust_cycle(&cd->cdd, deduction);
+	lc8951_adjust_cycles(&cd->cdc, deduction);
+	cd->graphics_cycle -= deduction;
+	if (cd->graphics_int_cycle != CYCLE_NEVER) {
+		if (cd->graphics_int_cycle > deduction) {
+			cd->graphics_int_cycle -= deduction;
+		} else {
+			cd->graphics_int_cycle = 0;
+		}
+	}
+}
+
+static uint16_t main_gate_read16(uint32_t address, void *vcontext)
+{
+	m68k_context *m68k = vcontext;
+	genesis_context *gen = m68k->system;
+	segacd_context *cd = gen->expansion;
+	uint32_t scd_cycle = gen_cycle_to_scd(m68k->current_cycle, gen);
+	scd_run(cd, scd_cycle);
+	uint32_t offset = (address & 0x1FF) >> 1;
+	switch (offset)
+	{
+	case GA_SUB_CPU_CTRL: {
+		uint16_t value = 0;
+		if (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN2) {
+			value |= BIT_IEN2;
+		}
+		if (cd->int2_cycle != CYCLE_NEVER) {
+			value |= BIT_IFL2;
+		}
+		if (can_main_access_prog(cd)) {
+			value |= BIT_SBRQ;
+		}
+		if (cd->reset) {
+			value |= BIT_SRES;
+		}
+		return value;
+	}
+	case GA_MEM_MODE:
+		//Main CPU can't read priority mode bits
+		return cd->gate_array[offset] & 0xFFE7;
+	case GA_HINT_VECTOR:
+		return cd->rom_mut[0x72/2];
+	case GA_CDC_HOST_DATA: {
+		uint16_t dst = cd->gate_array[GA_CDC_CTRL] >> 8 & 0x7;
+		if (dst == DST_MAIN_CPU) {
+			if (cd->gate_array[GA_CDC_CTRL] & BIT_DSR) {
+				printf("DSR cleared at %u (%u)\n", scd_cycle, cd->cdc.cycle);
+				cd->gate_array[GA_CDC_CTRL] &= ~BIT_DSR;
+				lc8951_resume_transfer(&cd->cdc, scd_cycle);
+			} else {
+				printf("Read of CDC host data with DSR clear at %u\n", scd_cycle);
+			}
+			calculate_target_cycle(cd->m68k);
+		}
+		return cd->gate_array[offset];
+	}
+	case GA_CDC_DMA_ADDR:
+		//TODO: open bus maybe?
+		return 0xFFFF;
+	default:
+		if (offset < GA_TIMER) {
+			if (offset == GA_CDC_CTRL) {
+				printf("CDC read(main): %X - %X @ %u (%u)\n", address, cd->gate_array[offset], m68k->current_cycle, scd_cycle);
+			} else if (offset >= GA_COMM_FLAG && offset <= GA_COMM_STATUS7) {
+				printf("COMM read(main): %X - %X @ %u (%u)\n", address, cd->gate_array[offset], m68k->current_cycle, scd_cycle);
+			}
+			return cd->gate_array[offset];
+		}
+		//TODO: open bus maybe?
+		return 0xFFFF;
+	}
+}
+
+static uint8_t main_gate_read8(uint32_t address, void *vcontext)
+{
+	uint16_t val = main_gate_read16(address & 0xFE, vcontext);
+	return address & 1 ? val : val >> 8;
+}
+
+static void dump_prog_ram(segacd_context *cd)
+{
+	static int dump_count;
+	char fname[256];
+	sprintf(fname, "prog_ram_%d.bin", dump_count++);
+	FILE *f = fopen(fname, "wb");
+	if (f) {
+		uint32_t last = 256*1024-1;
+		for(; last > 0; --last)
+		{
+			if (cd->prog_ram[last]) {
+				break;
+			}
+		}
+		for (uint32_t i = 0; i <= last; i++)
+		{
+			uint8_t pair[2];
+			pair[0] = cd->prog_ram[i] >> 8;
+			pair[1] = cd->prog_ram[i];
+			fwrite(pair, 1, sizeof(pair), f);
+		}
+
+		fclose(f);
+	}
+}
+
+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 scd_cycle = gen_cycle_to_scd(m68k->current_cycle, gen);
+	scd_run(cd, scd_cycle);
+	uint32_t reg = (address & 0x1FF) >> 1;
+	switch (reg)
+	{
+	case GA_SUB_CPU_CTRL: {
+		uint8_t old_access = can_main_access_prog(cd);
+		cd->busreq = value & BIT_SBRQ;
+		uint8_t old_reset = cd->reset;
+		cd->reset = value & BIT_SRES;
+		if (cd->reset && !old_reset) {
+			cd->need_reset = 1;
+		}
+		if (value & BIT_IFL2) {
+			cd->int2_cycle = scd_cycle;
+		}
+		/*cd->gate_array[reg] &= 0x7FFF;
+		cd->gate_array[reg] |= value & 0x8000;*/
+		uint8_t new_access = can_main_access_prog(cd);
+		uint32_t bank = cd->gate_array[GA_MEM_MODE] >> 6 & 0x3;
+		if (new_access) {
+			if (!old_access) {
+				m68k->mem_pointers[cd->memptr_start_index] = cd->prog_ram + bank * 0x10000;
+				m68k_invalidate_code_range(m68k, cd->base + 0x220000, cd->base + 0x240000);
+			}
+		} else if (old_access) {
+			m68k->mem_pointers[cd->memptr_start_index] = NULL;
+			m68k_invalidate_code_range(m68k, cd->base + 0x220000, cd->base + 0x240000);
+			m68k_invalidate_code_range(cd->m68k, bank * 0x20000, (bank + 1) * 0x20000);
+			if (!new_access) {
+				dump_prog_ram(cd);
+			}
+		}
+		break;
+	}
+	case GA_MEM_MODE: {
+		uint16_t changed = cd->gate_array[reg] ^ value;
+		//Main CPU can't write priority mode bits, MODE or RET
+		cd->gate_array[reg] &= 0x001F;
+		cd->gate_array[reg] |= value & 0xFFC0;
+		if ((cd->gate_array[reg] & BIT_MEM_MODE)) {
+			//1M mode
+			if (!(value & BIT_DMNA)) {
+				cd->gate_array[reg] |= BIT_DMNA;
+			}
+		} else {
+			//2M mode
+			if (changed & value & BIT_DMNA) {
+				cd->gate_array[reg] |= BIT_DMNA;
+				m68k->mem_pointers[cd->memptr_start_index + 1] = NULL;
+				m68k->mem_pointers[cd->memptr_start_index + 2] = NULL;
+				cd->m68k->mem_pointers[0] = cd->word_ram;
+				cd->gate_array[reg] &= ~BIT_RET;
+
+				m68k_invalidate_code_range(m68k, cd->base + 0x200000, cd->base + 0x240000);
+				m68k_invalidate_code_range(cd->m68k, 0x080000, 0x0C0000);
+			}
+		}
+		if (changed & MASK_PROG_BANK && can_main_access_prog(cd)) {
+			uint32_t bank = cd->gate_array[GA_MEM_MODE] >> 6 & 0x3;
+			m68k->mem_pointers[cd->memptr_start_index] = cd->prog_ram + bank * 0x10000;
+			m68k_invalidate_code_range(m68k, cd->base + 0x220000, cd->base + 0x240000);
+		}
+		break;
+	}
+	case GA_HINT_VECTOR:
+		cd->rom_mut[0x72/2] = value;
+		break;
+	case GA_COMM_FLAG:
+		//Main CPU can only write the upper byte;
+		cd->gate_array[reg] &= 0xFF;
+		cd->gate_array[reg] |= value & 0xFF00;
+		printf("COMM write(main): %X - %X @ %u (%u)\n", address, value, m68k->current_cycle, scd_cycle);
+		break;
+	case GA_COMM_CMD0:
+	case GA_COMM_CMD1:
+	case GA_COMM_CMD2:
+	case GA_COMM_CMD3:
+	case GA_COMM_CMD4:
+	case GA_COMM_CMD5:
+	case GA_COMM_CMD6:
+	case GA_COMM_CMD7:
+		//no effects for these other than saving the value
+		printf("COMM write(main): %X - %X @ %u (%u)\n", address, value, m68k->current_cycle, scd_cycle);
+		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;
+	switch (reg >> 1)
+	{
+	case GA_SUB_CPU_CTRL:
+		if (address & 1) {
+			value16 = value;
+		} else {
+			value16 = value << 8;
+			if (cd->reset) {
+				value16 |= BIT_SRES;
+			}
+			if (cd->busreq) {
+				value16 |= BIT_SBRQ;
+			}
+		}
+		break;
+	case GA_HINT_VECTOR:
+	case GA_COMM_FLAG:
+		//writes to these regs are always treated as word wide
+		value16 = value | (value << 8);
+		break;
+	default:
+		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, 0x01FEFF, 0xFFFFFF, .flags=MMAP_READ | MMAP_CODE, .write_16 = prog_ram_wp_write16, .write_8 = prog_ram_wp_write8},
+		{0x01FF00, 0x07FFFF, 0xFFFFFF, .flags=MMAP_READ | MMAP_WRITE | MMAP_CODE},
+		{0x080000, 0x0BFFFF, 0x03FFFF, .flags=MMAP_READ | MMAP_WRITE | MMAP_CODE | MMAP_PTR_IDX | MMAP_FUNC_NULL, .ptr_index = 0,
+			.read_16 = word_ram_2M_read16, .write_16 = word_ram_2M_write16, .read_8 = word_ram_2M_read8, .write_8 = word_ram_2M_write8},
+		{0x0C0000, 0x0DFFFF, 0x01FFFF, .flags=MMAP_READ | MMAP_WRITE | MMAP_CODE | MMAP_PTR_IDX | MMAP_FUNC_NULL, .ptr_index = 1,
+			.read_16 = word_ram_1M_read16, .write_16 = word_ram_1M_write16, .read_8 = word_ram_1M_read8, .write_8 = word_ram_1M_write8},
+		{0xFE0000, 0xFEFFFF, 0x003FFF, .flags=MMAP_READ | MMAP_WRITE | MMAP_ONLY_ODD},
+		{0xFF0000, 0xFF7FFF, 0x003FFF, .read_16 = pcm_read16, .write_16 = pcm_write16, .read_8 = pcm_read8, .write_8 = pcm_write8},
+		{0xFF8000, 0xFF81FF, 0x0001FF, .read_16 = sub_gate_read16, .write_16 = sub_gate_write16, .read_8 = sub_gate_read8, .write_8 = sub_gate_write8}
+	};
+
+	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);
+	cd->rom_mut[0x72/2] = 0xFFFF;
+
+	//memset(info, 0, sizeof(*info));
+	//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->word_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, sync_components);
+	cd->m68k = init_68k_context(mopts, NULL);
+	cd->m68k->system = cd;
+	cd->int2_cycle = CYCLE_NEVER;
+	cd->busreq = 1;
+	cd->busack = 1;
+	cd->need_reset = 1;
+	cd->reset = 1; //active low, so reset is not active on start
+	cd->memptr_start_index = 0;
+	cd->gate_array[1] = 1;
+	cd->gate_array[0x1B] = 0x100;
+	lc8951_init(&cd->cdc, handle_cdc_byte, cd);
+	if (media->chain && media->type != MEDIA_CDROM) {
+		media = media->chain;
+	}
+	cdd_mcu_init(&cd->cdd, media);
+	cd_graphics_init(cd);
+
+	return cd;
+}
+
+memmap_chunk *segacd_main_cpu_map(segacd_context *cd, uint8_t cart_boot, uint32_t *num_chunks)
+{
+	static memmap_chunk main_cpu_map[] = {
+		{0x000000, 0x01FFFF, 0x01FFFF, .flags=MMAP_READ},
+		{0x020000, 0x03FFFF, 0x01FFFF, .flags=MMAP_READ|MMAP_WRITE|MMAP_PTR_IDX|MMAP_FUNC_NULL|MMAP_CODE, .ptr_index = 0,
+			.read_16 = unmapped_prog_read16, .write_16 = unmapped_prog_write16, .read_8 = unmapped_prog_read8, .write_8 = unmapped_prog_write8},
+		{0x040000, 0x05FFFF, 0x01FFFF, .flags=MMAP_READ}, //first ROM alias
+		//TODO: additional ROM/prog RAM aliases
+		{0x200000, 0x21FFFF, 0x01FFFF, .flags=MMAP_READ|MMAP_WRITE|MMAP_PTR_IDX|MMAP_FUNC_NULL|MMAP_CODE, .ptr_index = 1,
+			.read_16 = unmapped_word_read16, .write_16 = unmapped_word_write16, .read_8 = unmapped_word_read8, .write_8 = unmapped_word_write8},
+		{0x220000, 0x23FFFF, 0x01FFFF, .flags=MMAP_READ|MMAP_WRITE|MMAP_PTR_IDX|MMAP_FUNC_NULL|MMAP_CODE, .ptr_index = 2,
+			.read_16 = cell_image_read16, .write_16 = cell_image_write16, .read_8 = cell_image_read8, .write_8 = cell_image_write8},
+		{0xA12000, 0xA12FFF, 0xFFFFFF, .read_16 = main_gate_read16, .write_16 = main_gate_write16, .read_8 = main_gate_read8, .write_8 = main_gate_write8}
+	};
+	for (int i = 0; i < sizeof(main_cpu_map) / sizeof(*main_cpu_map); i++)
+	{
+		if (main_cpu_map[i].start < 0x800000) {
+			if (cart_boot) {
+				main_cpu_map[i].start  |= 0x400000;
+				main_cpu_map[i].end  |= 0x400000;
+			} else {
+				main_cpu_map[i].start  &= 0x3FFFFF;
+				main_cpu_map[i].end  &= 0x3FFFFF;
+			}
+		}
+	}
+	//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->word_ram;
+	main_cpu_map[4].buffer = cd->word_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	Sun Jan 30 22:29:29 2022 -0800
@@ -0,0 +1,52 @@
+#ifndef SEGACD_H_
+#define SEGACD_H_
+#include <stdint.h>
+#include "genesis.h"
+#include "lc8951.h"
+#include "cdd_mcu.h"
+
+typedef struct {
+	m68k_context    *m68k;
+	system_media    *media;
+	genesis_context *genesis;
+	uint16_t        gate_array[0x100];
+	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        *word_ram;
+	uint8_t         *pcm_ram;
+	uint8_t         *bram;
+	uint32_t        stopwatch_cycle;
+	uint32_t        int2_cycle;
+	uint32_t        graphics_int_cycle;
+	uint32_t        periph_reset_cycle;
+	uint32_t        graphics_cycle;
+	uint32_t        base;
+	uint32_t        graphics_x;
+	uint32_t        graphics_y;
+	uint32_t        graphics_dx;
+	uint32_t        graphics_dy;
+	uint16_t        graphics_dst_x;
+	uint8_t         graphics_pixels[4];
+	uint8_t         timer_pending;
+	uint8_t         timer_value;
+	uint8_t         busreq;
+	uint8_t         busack;
+	uint8_t         reset;
+	uint8_t         need_reset;
+	uint8_t         memptr_start_index;
+	lc8951          cdc;
+	cdd_mcu         cdd;
+	uint8_t         cdc_dst_low;
+	uint8_t         cdc_int_ack;
+	uint8_t         graphics_step;
+	uint8_t         graphics_dst_y;
+} 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, uint8_t cart_boot, uint32_t *num_chunks);
+uint32_t gen_cycle_to_scd(uint32_t cycle, genesis_context *gen);
+void scd_run(segacd_context *cd, uint32_t cycle);
+void scd_adjust_cycle(segacd_context *cd, uint32_t deduction);
+
+#endif //SEGACD_H_
--- a/sf2_50mhz_analysis.txt	Sat Jan 01 18:54:46 2022 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,173 +0,0 @@
-HSYNC End
-Plane A: D13C
-??slot??
-tile
-tile
-Plane B: F29C
-?sprite tile slot?
-tile
-tile
-Plane A: D100
-68K Slot
-tile
-tile
-Plane B: F2A0
-?sprite table slot?
-tile
-tile
-Plane A: D104
-68K slot
-tile
-tile
-Plane B: F2A4
-?sprite table slot?
-tile
-tile
-Plane A: D108
-68K slot
-?tile?
-?tile?
-Plane B: F2A8
-?sprite table slot?
-tile
-tile
-Plane A: D10C
-refresh
-?tile?
-?tile?
-Plane B: F22C
-?sprite table slot?
-tile
-tile
-Plane A: D110
-68K slot
-?tile?
-?tile?
-Plane B: F230
-?sprite table slot?
-tile
-tile
-Plane A: D114
-68K slot
-?tile?
-?tile?
-Plane B: F2F4
-?sprite table slot?
-tile
-tile
-Plane A: D118
-68K slot
-?tile?
-?tile?
-Plane B: F238
-?sprite table slot?
-ttile tile
-Plane A: D11C
-refresh
-?tile?
-?tile?
-Plane B: F23C
-?sprite table slot?
-tile
-tile
-Plane A: D120
-68K slot
-?tile?
-?tile?
-Plane B: F240
-?sprite table slot?
-tile
-tile
-Plane A: D124
-68K Slot
-?tile?
-?tile?
-Plane B: F244
-?sprite table slot?
-tile
-tile
-Plane A: D128
-68K Slot
-?tile?
-?tile?
-Plane B: F3C8
-?sprite table slot?
-tile
-tile
-Plane A: D12C
-refresh
-?tile?
-?tile?
-Plane B: F24C
-?sprite table slot?
-tile
-tile
-Plane A: D130
-68K slot
-?tile?
-?tile?
-Plane B: F250
-?sprite table slot?
-tile
-tile
-Plane A: D134
-68K Slot
-?tile?
-?tile?
-Plane B: F254
-?sprite table slot?
-tile
-tile
-Plane A: D138
-68K slot
-?tile?
-?tile?
-Plane B: F3D8
-?sprite table slot?
-tile
-tile
-Plane A: D13C
-refresh
-tile
-tile
-Plane B: F3DC
-?sprite table slot?
-tile
-tile
----------------
-68K Slot
-68K Slot
-sprite tile slot
-sprite tile slot
-sprite tile slot
-sprite tile slot
-?sprite tile slot?
-?sprite tile slot?
-?sprite tile slot?
-?sprite tile slot?
-?sprite tile slot?
-?sprite tile slot?
-?sprite tile slot?
-?sprite tile slot?
-?sprite tile slot?
-?sprite tile slot?
-68K Slot
-?sprite tile slot?
-?sprite tile slot?
-?sprite tile slot?
-?sprite tile slot?
-?sprite tile slot?
-HSYNC Start
-?sprite tile slot?
-?sprite tile slot?
-?sprite tile slot?
-?sprite tile slot?
-?sprite tile slot?
-?sprite tile slot?
-?unkown tile slot?
-68K Slot
-Horizontal Scroll
-?sprite tile slot?
-?sprite tile slot?
-?sprite tile slot?
-?unkown tile slot?
--- a/sf2_vram_map.txt	Sat Jan 01 18:54:46 2022 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-0000: single color blocks
-0200: Random symbols????
-0400-0C00: Font
-0C00-0E??: Score font
-0E??: Player name displays
-
-2000-3000: floor (mostly, some garbage around 2800 and 2A00)
-3000-6C00: background
-6C00-8000: not graphics
-8000-~8A00: bat/bird sprites, special attack text
-8A00-9000: ?????
-9000-9200: More special attack text
-9200-9600: another font
-9800-AA00: special attack effects?
-AC00-B000: ????
-B000-B500: character sprite
-????
-C000-C500: chracter sprite
-D000-DA00: window name table
-DA00-DC00: Sprite attribute table
-DC00-E000: horizontal scroll data
-E000-????: plane A&B name table
-C500-FFFF: not graphics
-
-VDP Registers:
-Mode
-00 - 14
-01 - 64
-Scroll A Name Table Address: E000
-02 - 38
-Window Name Table Address: D000
-03 - 34
-Scroll B Name Table Address: E000
-04 - 07
-Sprite Attribute Table Address: DA00
-05 - 6D
-06 - 00
-Backdrop color - 0
-07 - 00
-08 - 00
-09 - 00
-HINT Counter
-0A - AF
-Mode - Full screen vertical scroll, line horizontal scroll, external ints disabled, 32 cell display, no interlacing
-0B - 03
-0C - 00
-H Scroll Data Address: DC00
-0D - 37
-0E - 00
-Auto increment
-0F - 02
-Scroll Size
-10 - 11
-Window H Pos
-11 - 00
-Window V Pos
-12 - 05
-DMA transfer length
-13 - 00
-14 - 00
-DMA source address and mode
-15 - DB
-16 - CF
-17 - 7F
--- a/system.c	Sat Jan 01 18:54:46 2022 -0800
+++ b/system.c	Sun Jan 30 22:29:29 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	Sun Jan 30 22:29:29 2022 -0800
@@ -2,6 +2,7 @@
 #define SYSTEM_H_
 #include <stddef.h>
 #include <stdint.h>
+#include <stdio.h>
 
 typedef struct system_header system_header;
 typedef struct system_media system_media;
@@ -10,6 +11,7 @@
 	SYSTEM_UNKNOWN,
 	SYSTEM_GENESIS,
 	SYSTEM_GENESIS_PLAYER,
+	SYSTEM_SEGACD,
 	SYSTEM_SMS,
 	SYSTEM_SMS_PLAYER,
 	SYSTEM_JAGUAR,
@@ -38,53 +40,75 @@
 #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;
+
+typedef enum {
+	TRACK_AUDIO,
+	TRACK_DATA
+} track_type;
+
+typedef struct {
+	uint32_t   fake_pregap;
+	uint32_t   pregap_lba;
+	uint32_t   start_lba;
+	uint32_t   end_lba;
+	track_type type;
+} track_info;
+
 struct system_media {
 	void         *buffer;
 	char         *dir;
 	char         *name;
 	char         *extension;
 	system_media *chain;
+	track_info   *tracks;
+	FILE         *f;
+	uint32_t     num_tracks;
 	uint32_t     size;
+	media_type   type;
 };
 
 #define OPT_ADDRESS_LOG (1U << 31U)