changeset 2052:3748a2a8a4b7

Support Sega mapper without 'SEGA SSF' in header or ROM DB entry and implement a subset of the extended Sega mapper implemented in the Mega Everdrive when 'SEGA SSF' is present
author Michael Pavone <pavone@retrodev.com>
date Sat, 01 Jan 2022 18:54:46 -0800
parents 97bfb6089274
children 3414a4423de1 cc13c100b027
files genesis.c genesis.h rom.db romdb.c romdb.h sega_mapper.c sega_mapper.h
diffstat 7 files changed, 284 insertions(+), 91 deletions(-) [+]
line wrap: on
line diff
--- a/genesis.c	Fri Dec 31 13:03:21 2021 -0800
+++ b/genesis.c	Sat Jan 01 18:54:46 2022 -0800
@@ -38,7 +38,7 @@
 #ifdef IS_LIB
 #define MAX_SOUND_CYCLES (MCLKS_PER_YM*NUM_OPERATORS*6*4)
 #else
-#define MAX_SOUND_CYCLES 100000	
+#define MAX_SOUND_CYCLES 100000
 #endif
 
 #ifdef NEW_CORE
@@ -56,53 +56,53 @@
 		start_section(buf, SECTION_68000);
 		m68k_serialize(gen->m68k, m68k_pc, buf);
 		end_section(buf);
-		
+
 		start_section(buf, SECTION_Z80);
 		z80_serialize(gen->z80, buf);
 		end_section(buf);
 	}
-	
+
 	start_section(buf, SECTION_VDP);
 	vdp_serialize(gen->vdp, buf);
 	end_section(buf);
-	
+
 	start_section(buf, SECTION_YM2612);
 	ym_serialize(gen->ym, buf);
 	end_section(buf);
-	
+
 	start_section(buf, SECTION_PSG);
 	psg_serialize(gen->psg, buf);
 	end_section(buf);
-	
+
 	if (all) {
 		start_section(buf, SECTION_GEN_BUS_ARBITER);
 		save_int8(buf, gen->z80->reset);
 		save_int8(buf, gen->z80->busreq);
 		save_int16(buf, gen->z80_bank_reg);
 		end_section(buf);
-		
+
 		start_section(buf, SECTION_SEGA_IO_1);
 		io_serialize(gen->io.ports, buf);
 		end_section(buf);
-		
+
 		start_section(buf, SECTION_SEGA_IO_2);
 		io_serialize(gen->io.ports + 1, buf);
 		end_section(buf);
-		
+
 		start_section(buf, SECTION_SEGA_IO_EXT);
 		io_serialize(gen->io.ports + 2, buf);
 		end_section(buf);
-		
+
 		start_section(buf, SECTION_MAIN_RAM);
 		save_int8(buf, RAM_WORDS * 2 / 1024);
 		save_buffer16(buf, gen->work_ram, RAM_WORDS);
 		end_section(buf);
-		
+
 		start_section(buf, SECTION_SOUND_RAM);
 		save_int8(buf, Z80_RAM_BYTES / 1024);
 		save_buffer8(buf, gen->zram, Z80_RAM_BYTES);
 		end_section(buf);
-		
+
 		if (gen->version_reg & 0xF) {
 			//only save TMSS info if it's present
 			//that will allow a state saved on a model lacking TMSS
@@ -112,7 +112,7 @@
 			save_buffer16(buf, gen->tmss_lock, 2);
 			end_section(buf);
 		}
-		
+
 		cart_serialize(&gen->header, buf);
 	}
 }
@@ -250,7 +250,7 @@
 	if ((address >= 0xA00000 && address < 0xB00000) || (address >= 0xC00000 && address <= 0xE00000)) {
 		return 0;
 	}
-	
+
 	//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);
 }
@@ -290,7 +290,7 @@
 				uint32_t next_eint_port0 = io_next_interrupt(gen->io.ports, context->current_cycle);
 				uint32_t next_eint_port1 = io_next_interrupt(gen->io.ports + 1, context->current_cycle);
 				uint32_t next_eint_port2 = io_next_interrupt(gen->io.ports + 2, context->current_cycle);
-				uint32_t next_eint = next_eint_port0 < next_eint_port1 
+				uint32_t next_eint = next_eint_port0 < next_eint_port1
 					? (next_eint_port0 < next_eint_port2 ? next_eint_port0 : next_eint_port2)
 					: (next_eint_port1 < next_eint_port2 ? next_eint_port1 : next_eint_port2);
 				if (next_eint != CYCLE_NEVER) {
@@ -310,7 +310,7 @@
 		printf("int cycle changed to: %d, level: %d @ %d(%d), frame: %d, vcounter: %d, hslot: %d, mask: %d, hint_counter: %d\n", context->int_cycle, context->int_num, v_context->cycles, context->current_cycle, v_context->frame, v_context->vcounter, v_context->hslot, context->status & 0x7, v_context->hint_counter);
 		old_int_cycle = context->int_cycle;
 	}*/
-	
+
 	if (context->status & M68K_STATUS_TRACE || context->trace_pending) {
 		context->target_cycle = context->current_cycle;
 		return;
@@ -335,7 +335,7 @@
 		} else {
 			context->target_cycle = context->sync_cycle = context->current_cycle;
 		}
-		
+
 	}
 	/*printf("Cyc: %d, Trgt: %d, Int Cyc: %d, Int: %d, Mask: %X, V: %d, H: %d, HICount: %d, HReg: %d, Line: %d\n",
 		context->current_cycle, context->target_cycle, context->int_cycle, context->int_num, (context->status & 0x7),
@@ -610,7 +610,7 @@
 							gen->bus_busy = 0;
 						}
 					}
-					
+
 					if (blocked < 0) {
 						blocked = vdp_control_port_write(v_context, value);
 					} else {
@@ -1308,7 +1308,7 @@
 	} else {
 		gen->version_reg = NO_DISK | USA;
 	}
-	
+
 	if (region & HZ50) {
 		gen->normal_clock = MCLKS_PAL;
 		gen->soft_flush_cycles = MCLKS_LINE * 262 / 3 + 2;
@@ -1514,6 +1514,9 @@
 	free(gen->header.save_dir);
 	free_rom_info(&gen->header.info);
 	free(gen->lock_on);
+	if (gen->save_type != SAVE_NONE && gen->mapper_type != MAPPER_SEGA_MED_V2) {
+		free(gen->save_storage);
+	}
 	free(gen);
 }
 
@@ -1578,7 +1581,7 @@
 	render_audio_source_gaindb(gen->psg->audio, config_gain ? atof(config_gain) : 0.0f);
 	config_gain = tern_find_path(config, "audio\0fm_gain\0", TVAL_PTR).ptrval;
 	render_audio_source_gaindb(gen->ym->audio, config_gain ? atof(config_gain) : 0.0f);
-	
+
 	char *config_dac = tern_find_path_default(config, "audio\0fm_dac\0", (tern_val){.ptrval="zero_offset"}, TVAL_PTR).ptrval;
 	ym_enable_zero_offset(gen->ym, !strcmp(config_dac, "zero_offset"));
 }
@@ -1621,7 +1624,7 @@
 	if (gen->tmss) {
 		return gen->tmss_write_16(address, context, value);
 	}
-	
+
 	return context;
 }
 
@@ -1632,7 +1635,7 @@
 	if (gen->tmss) {
 		return gen->tmss_write_8(address, context, value);
 	}
-	
+
 	return context;
 }
 
@@ -1670,7 +1673,7 @@
 		*dest = value;
 		m68k_handle_code_write(address, m68k);
 	}
-	
+
 	return context;
 }
 
@@ -1688,7 +1691,7 @@
 #endif
 		m68k_handle_code_write(address & ~1, m68k);
 	}
-	
+
 	return context;
 }
 
@@ -1797,10 +1800,10 @@
 	gen->max_cycles = config_cycles ? atoi(config_cycles) : DEFAULT_SYNC_INTERVAL;
 	gen->int_latency_prev1 = MCLKS_PER_68K * 32;
 	gen->int_latency_prev2 = MCLKS_PER_68K * 16;
-	
+
 	render_set_video_standard((gen->version_reg & HZ50) ? VID_PAL : VID_NTSC);
 	event_system_start(SYSTEM_GENESIS, (gen->version_reg & HZ50) ? VID_PAL : VID_NTSC, rom->name);
-	
+
 	gen->ym = malloc(sizeof(ym2612_context));
 	char *fm = tern_find_ptr_default(model, "fm", "discrete 2612");
 	if (!strcmp(fm + strlen(fm) -4, "3834")) {
@@ -1810,7 +1813,7 @@
 
 	gen->psg = malloc(sizeof(psg_context));
 	psg_init(gen->psg, gen->master_clock, MCLKS_PER_PSG);
-	
+
 	set_audio_config(gen);
 
 	z80_map[0].buffer = gen->zram = calloc(1, Z80_RAM_BYTES);
@@ -1881,9 +1884,9 @@
 	} else {
 		gen->save_storage = NULL;
 	}
-	
+
 	gen->mapper_start_index = rom->mapper_start_index;
-	
+
 	//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;
@@ -2031,7 +2034,7 @@
 	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 < rom->map_chunks; i++)
 	{
@@ -2039,7 +2042,7 @@
 			gen->m68k->mem_pointers[rom->map[i].ptr_index] = rom->map[i].buffer;
 		}
 	}
-	
+
 	if (gen->mapper_type == MAPPER_SEGA) {
 		//initialize bank registers
 		for (int i = 1; i < sizeof(gen->bank_regs); i++)
--- a/genesis.h	Fri Dec 31 13:03:21 2021 -0800
+++ b/genesis.h	Sat Jan 01 18:54:46 2022 -0800
@@ -61,11 +61,11 @@
 	uint32_t        last_flush_cycle;
 	uint32_t        soft_flush_cycles;
 	uint32_t        tmss_write_offset;
-	uint8_t         bank_regs[8];
 	uint16_t        z80_bank_reg;
 	uint16_t        tmss_lock[2];
 	uint16_t        mapper_start_index;
 	uint8_t         mapper_type;
+	uint8_t         bank_regs[9];
 	uint8_t         save_type;
 	sega_io         io;
 	uint8_t         version_reg;
--- a/rom.db	Fri Dec 31 13:03:21 2021 -0800
+++ b/rom.db	Sat Jan 01 18:54:46 2022 -0800
@@ -779,7 +779,7 @@
 			last 3FFFFF
 		}
 	}
-	
+
 }
 MK-1563 {
 	name Sonic & Knuckles
@@ -1287,7 +1287,7 @@
 }
 
 7313c20071de0ab1cd84ac1352cb0ed1c4a4afa8 {
-	#This appears to be an underdump, but it seems to be the only copy floating around 
+	#This appears to be an underdump, but it seems to be the only copy floating around
 	name 12-in-1
 	map {
 		0 {
@@ -1377,20 +1377,6 @@
 		}
 	}
 }
-NETO-001 {
-	name Sonic Delta
-	map {
-		0 {
-			device ROM
-			last 7FFFF
-		}
-		80000 {
-			device Sega mapper
-			last 3FFFFF
-			offset 80000
-		}
-	}
-}
 5e5ca20b39122c86b8f662bd8014af674f9dbed7 {
 	name Rock Heaven
 	map {
@@ -1422,7 +1408,7 @@
 			value A000
 			last 500209
 		}
-		
+
 	}
 }
 T-122026 {
--- a/romdb.c	Fri Dec 31 13:03:21 2021 -0800
+++ b/romdb.c	Sat Jan 01 18:54:46 2022 -0800
@@ -80,6 +80,7 @@
 	{
 	case MAPPER_SEGA:
 	case MAPPER_SEGA_SRAM:
+	case MAPPER_SEGA_MED_V2:
 		sega_mapper_serialize(gen, buf);
 		break;
 	case MAPPER_REALTEC:
@@ -126,7 +127,7 @@
 	//TODO: Should probably prefer the title field that corresponds to the user's region preference
 	uint8_t *last = rom + TITLE_END - 1;
 	uint8_t *src = rom + TITLE_START;
-	
+
 	for (;;)
 	{
 		while (last > src && (*last <=  0x20 || *last >= 0x80))
@@ -169,7 +170,7 @@
 uint8_t region_bits[] = {REGION_J, REGION_U, REGION_E, REGION_J|REGION_U|REGION_E};
 
 uint8_t translate_region_char(uint8_t c)
-{	
+{
 	for (int i = 0; i < sizeof(region_bits); i++)
 	{
 		if (c == region_chars[i]) {
@@ -226,7 +227,7 @@
 
 uint8_t has_ram_header(uint8_t *rom, uint32_t rom_size)
 {
-	return rom_size >= (RAM_END + 4) && rom[RAM_ID] == 'R' && rom[RAM_ID + 1] == 'A'; 
+	return rom_size >= (RAM_END + 4) && rom[RAM_ID] == 'R' && rom[RAM_ID + 1] == 'A';
 }
 
 uint32_t read_ram_header(rom_info *info, uint8_t *rom)
@@ -253,44 +254,86 @@
 void add_memmap_header(rom_info *info, uint8_t *rom, uint32_t size, memmap_chunk const *base_map, int base_chunks)
 {
 	uint32_t rom_end = get_u32be(rom + ROM_END) + 1;
+	uint32_t rom_end_raw = rom_end;
 	if (size > rom_end) {
 		rom_end = size;
 	} else if (rom_end > nearest_pow2(size)) {
 		rom_end = nearest_pow2(size);
 	}
 	info->save_type = SAVE_NONE;
-	if (size >= 0x80000 && !memcmp("SEGA SSF", rom + 0x100, 8)) {
+	uint8_t is_med_ssf = size >= 0x108 && !memcmp("SEGA SSF", rom + 0x100, 8);
+	if (is_med_ssf || (size >= 0x400000 && rom_end_raw <= 0x400000)) {
+		if (is_med_ssf && rom_end < 16*1024*1024) {
+			info->rom = rom = realloc(rom, 16*1024*1024);
+		}
 		info->mapper_start_index = 0;
-		info->mapper_type = MAPPER_SEGA;
+		info->mapper_type = is_med_ssf ? MAPPER_SEGA_MED_V2 : MAPPER_SEGA;
 		info->map_chunks = base_chunks + 9;
 		info->map = malloc(sizeof(memmap_chunk) * info->map_chunks);
 		memset(info->map, 0, sizeof(memmap_chunk)*9);
 		memcpy(info->map+9, base_map, sizeof(memmap_chunk) * base_chunks);
-		
-		info->map[0].start = 0;
-		info->map[0].end = 0x80000;
-		info->map[0].mask = 0xFFFFFF;
-		info->map[0].flags = MMAP_READ;
-		info->map[0].buffer = rom;
-		
-		if (has_ram_header(rom, size)){
-			read_ram_header(info, rom);
+
+		int i;
+		uint16_t map_flags;
+		if (is_med_ssf) {
+			i = 0;
+			map_flags = info->map[i].flags = MMAP_READ | MMAP_PTR_IDX | MMAP_CODE;
+			info->save_type = RAM_FLAG_BOTH;
+			info->save_size = 256*1024;
+			info->save_mask = info->save_size - 1;
+			info->save_buffer = rom + 16*1024*1024 - 256*1024;
+		} else {
+			i = 1;
+			map_flags = info->map[i].flags = MMAP_READ | MMAP_PTR_IDX | MMAP_CODE | MMAP_FUNC_NULL;
+			info->map[0].start = 0;
+			info->map[0].end = 0x80000;
+			info->map[0].mask = 0xFFFFFF;
+			info->map[0].flags = MMAP_READ;
+			info->map[0].buffer = rom;
+
+			if (has_ram_header(rom, size)){
+				read_ram_header(info, rom);
+			}
 		}
-		
-		for (int i = 1; i < 8; i++)
+		static const write_8_fun med_w8[] = {
+			write_med_ram0_b,
+			write_med_ram1_b,
+			write_med_ram2_b,
+			write_med_ram3_b,
+			write_med_ram4_b,
+			write_med_ram5_b,
+			write_med_ram6_b,
+			write_med_ram7_b,
+		};
+		static const write_16_fun med_w16[] = {
+			write_med_ram0_w,
+			write_med_ram1_w,
+			write_med_ram2_w,
+			write_med_ram3_w,
+			write_med_ram4_w,
+			write_med_ram5_w,
+			write_med_ram6_w,
+			write_med_ram7_w,
+		};
+
+		for (; i < 8; i++)
 		{
 			info->map[i].start = i * 0x80000;
 			info->map[i].end = (i + 1) * 0x80000;
 			info->map[i].mask = 0x7FFFF;
 			info->map[i].buffer = (i + 1) * 0x80000 <= size ? rom + i * 0x80000 : rom;
 			info->map[i].ptr_index = i;
-			info->map[i].flags = MMAP_READ | MMAP_PTR_IDX | MMAP_CODE | MMAP_FUNC_NULL;
-			
-			info->map[i].read_16 = (read_16_fun)read_sram_w;//these will only be called when mem_pointers[i] == NULL
-			info->map[i].read_8 = (read_8_fun)read_sram_b;
-			info->map[i].write_16 = (write_16_fun)write_sram_area_w;//these will be called all writes to the area
-			info->map[i].write_8 = (write_8_fun)write_sram_area_b;
-			
+			info->map[i].flags = map_flags;
+
+			info->map[i].read_16 = is_med_ssf ? NULL : (read_16_fun)read_sram_w;//these will only be called when mem_pointers[i] == NULL
+			info->map[i].read_8 = is_med_ssf ? NULL : (read_8_fun)read_sram_b;
+			if (is_med_ssf) {
+				info->map[i].write_16 = med_w16[i];
+				info->map[i].write_8 = med_w8[i];
+			} else {
+				info->map[i].write_16 = (write_16_fun)write_sram_area_w;//these will be called all writes to the area
+				info->map[i].write_8 = (write_8_fun)write_sram_area_b;
+			}
 		}
 		info->map[8].start = 0xA13000;
 		info->map[8].end = 0xA13100;
@@ -327,7 +370,7 @@
 		info->map[1].end = 0xA130D0;
 		info->map[1].mask = 0xFFFFFF;
 		if (!strcmp(
-			"on", 
+			"on",
 			tern_find_path_default(config, "system\0megawifi\0", (tern_val){.ptrval="off"}, TVAL_PTR).ptrval)
 		) {
 			info->map[1].write_16 = megawifi_write_w;
@@ -388,7 +431,7 @@
 				info->map[1].write_16 = (write_16_fun)write_sram_area_w;//these will be called all writes to the area
 				info->map[1].write_8 = (write_8_fun)write_sram_area_b;
 				info->map[1].buffer = rom + 0x200000;
-				
+
 				//Last entry in the base map is a catch all one that needs to be
 				//after all the other entries
 				memmap_chunk *unused = info->map + info->map_chunks - 2;
@@ -405,7 +448,7 @@
 			return;
 		}
 	}
-	
+
 	info->map_chunks = base_chunks + 1;
 	info->map = malloc(sizeof(memmap_chunk) * info->map_chunks);
 	memset(info->map, 0, sizeof(memmap_chunk));
@@ -743,7 +786,7 @@
 		}
 	} else if (!strcmp(dtype, "NOR")) {
 		process_nor_def(key, state);
-		
+
 		map->write_16 = nor_flash_write_w;
 		map->write_8 = nor_flash_write_b;
 		map->read_16 = nor_flash_read_w;
@@ -868,7 +911,7 @@
 		map->write_8 = write_multi_game_b;
 	} else if (!strcmp(dtype, "megawifi")) {
 		if (!strcmp(
-			"on", 
+			"on",
 			tern_find_path_default(config, "system\0megawifi\0", (tern_val){.ptrval="off"}, TVAL_PTR).ptrval)
 		) {
 			map->write_16 = megawifi_write_w;
@@ -965,14 +1008,14 @@
 			info.num_eeprom = 0;
 			memset(info.map, 0, sizeof(memmap_chunk) * info.map_chunks);
 			map_iter_state state = {
-				.info = &info, 
-				.rom = rom, 
+				.info = &info,
+				.rom = rom,
 				.lock_on = lock_on,
 				.root = entry,
 				.rom_db = rom_db,
-				.rom_size = rom_size, 
+				.rom_size = rom_size,
 				.lock_on_size = lock_on_size,
-				.index = 0, 
+				.index = 0,
 				.num_els = info.map_chunks - base_chunks,
 				.ptr_index = 0
 			};
--- a/romdb.h	Fri Dec 31 13:03:21 2021 -0800
+++ b/romdb.h	Sat Jan 01 18:54:46 2022 -0800
@@ -48,7 +48,8 @@
 	MAPPER_REALTEC,
 	MAPPER_XBAND,
 	MAPPER_MULTI_GAME,
-	MAPPER_JCART
+	MAPPER_JCART,
+	MAPPER_SEGA_MED_V2
 };
 
 
--- a/sega_mapper.c	Fri Dec 31 13:03:21 2021 -0800
+++ b/sega_mapper.c	Sat Jan 01 18:54:46 2022 -0800
@@ -1,4 +1,5 @@
 #include "genesis.h"
+#include "util.h"
 
 uint16_t read_sram_w(uint32_t address, m68k_context * context)
 {
@@ -43,6 +44,14 @@
 m68k_context * write_sram_area_w(uint32_t address, m68k_context * context, uint16_t value)
 {
 	genesis_context * gen = context->system;
+	if (gen->mapper_type == MAPPER_SEGA_MED_V2) {
+		if (gen->bank_regs[8] & 0x20) {
+			uint32_t bank = address >> 19;
+			address &= 0x7FFFF;
+			context->mem_pointers[gen->mapper_start_index + bank][address >> 1] = value;
+		}
+		return context;
+	}
 	if ((gen->bank_regs[0] & 0x3) == 1) {
 		address &= gen->save_ram_mask;
 		switch(gen->save_type)
@@ -87,14 +96,128 @@
 	return context;
 }
 
+static void* write_med_ram_w(uint32_t address, void *vcontext, uint16_t value, uint16_t bank)
+{
+	m68k_context *context = vcontext;
+	genesis_context * gen = context->system;
+	if (gen->bank_regs[8] & 0x20) {
+		context->mem_pointers[gen->mapper_start_index + bank][address >> 1] = value;
+		address += bank * 0x80000;
+		m68k_invalidate_code_range(gen->m68k, address, address + 2);
+	}
+	return vcontext;
+}
+
+void* write_med_ram0_w(uint32_t address, void *vcontext, uint16_t value)
+{
+	return write_med_ram_w(address, vcontext, value, 0);
+}
+
+void* write_med_ram1_w(uint32_t address, void *vcontext, uint16_t value)
+{
+	return write_med_ram_w(address, vcontext, value, 1);
+}
+
+void* write_med_ram2_w(uint32_t address, void *vcontext, uint16_t value)
+{
+	return write_med_ram_w(address, vcontext, value, 2);
+}
+
+void* write_med_ram3_w(uint32_t address, void *vcontext, uint16_t value)
+{
+	return write_med_ram_w(address, vcontext, value, 3);
+}
+
+void* write_med_ram4_w(uint32_t address, void *vcontext, uint16_t value)
+{
+	return write_med_ram_w(address, vcontext, value, 4);
+}
+
+void* write_med_ram5_w(uint32_t address, void *vcontext, uint16_t value)
+{
+	return write_med_ram_w(address, vcontext, value, 5);
+}
+
+void* write_med_ram6_w(uint32_t address, void *vcontext, uint16_t value)
+{
+	return write_med_ram_w(address, vcontext, value, 6);
+}
+
+void* write_med_ram7_w(uint32_t address, void *vcontext, uint16_t value)
+{
+	return write_med_ram_w(address, vcontext, value, 7);
+}
+
+static void* write_med_ram_b(uint32_t address, void *vcontext, uint8_t value, uint16_t bank)
+{
+	m68k_context *context = vcontext;
+	genesis_context * gen = context->system;
+	if (gen->bank_regs[8] & 0x20) {
+		((uint8_t*)context->mem_pointers[gen->mapper_start_index + bank])[address ^ 1] = value;
+		address += bank * 0x80000;
+		m68k_invalidate_code_range(gen->m68k, address, address + 1);
+	}
+	return vcontext;
+}
+
+void* write_med_ram0_b(uint32_t address, void *vcontext, uint8_t value)
+{
+	return write_med_ram_b(address, vcontext, value, 0);
+}
+
+void* write_med_ram1_b(uint32_t address, void *vcontext, uint8_t value)
+{
+	return write_med_ram_b(address, vcontext, value, 1);
+}
+
+void* write_med_ram2_b(uint32_t address, void *vcontext, uint8_t value)
+{
+	return write_med_ram_b(address, vcontext, value, 2);
+}
+
+void* write_med_ram3_b(uint32_t address, void *vcontext, uint8_t value)
+{
+	return write_med_ram_b(address, vcontext, value, 3);
+}
+
+void* write_med_ram4_b(uint32_t address, void *vcontext, uint8_t value)
+{
+	return write_med_ram_b(address, vcontext, value, 4);
+}
+
+void* write_med_ram5_b(uint32_t address, void *vcontext, uint8_t value)
+{
+	return write_med_ram_b(address, vcontext, value, 5);
+}
+
+void* write_med_ram6_b(uint32_t address, void *vcontext, uint8_t value)
+{
+	return write_med_ram_b(address, vcontext, value, 6);
+}
+
+void* write_med_ram7_b(uint32_t address, void *vcontext, uint8_t value)
+{
+	return write_med_ram_b(address, vcontext, value, 7);
+}
+
 m68k_context * write_bank_reg_w(uint32_t address, m68k_context * context, uint16_t value)
 {
 	genesis_context * gen = context->system;
 	address &= 0xE;
 	address >>= 1;
-	gen->bank_regs[address] = value;
 	if (!address) {
-		if (value & 1) {
+		if (gen->mapper_type == MAPPER_SEGA_MED_V2) {
+			if (!value & 0x8000) {
+				//writes without protection bit set are ignored
+				return context;
+			}
+			gen->bank_regs[8] = value >> 8;
+			void *new_ptr = gen->cart + 0x40000*(value & 0x1F);
+			if (context->mem_pointers[gen->mapper_start_index] != new_ptr) {
+				m68k_invalidate_code_range(gen->m68k, 0, 0x80000);
+				context->mem_pointers[gen->mapper_start_index] = new_ptr;
+			}
+		} else if (value & 1) {
 			//Used for games that only use the mapper for SRAM
 			if (context->mem_pointers[gen->mapper_start_index]) {
 				gen->mapper_temp = context->mem_pointers[gen->mapper_start_index];
@@ -116,19 +239,30 @@
 				context->mem_pointers[gen->mapper_start_index + i] = gen->cart + 0x40000*gen->bank_regs[i];
 			}
 		}
-	} else if (gen->mapper_type == MAPPER_SEGA) {
-		void *new_ptr = gen->cart + 0x40000*value;
+	} else if (gen->mapper_type != MAPPER_SEGA_SRAM) {
+		uint32_t mask = ((gen->mapper_type == MAPPER_SEGA_MED_V2 ? (16 *1024 * 1024) : nearest_pow2(gen->header.info.rom_size)) >> 1) - 1;
+		void *new_ptr = gen->cart + ((0x40000*value) & mask);
 		if (context->mem_pointers[gen->mapper_start_index + address] != new_ptr) {
 			m68k_invalidate_code_range(gen->m68k, address * 0x80000, (address + 1) * 0x80000);
 			context->mem_pointers[gen->mapper_start_index + address] = new_ptr;
 		}
 	}
+	gen->bank_regs[address] = value;
 	return context;
 }
 
 m68k_context * write_bank_reg_b(uint32_t address, m68k_context * context, uint8_t value)
 {
-	if (address & 1) {
+	genesis_context * gen = context->system;
+	if (gen->mapper_type == MAPPER_SEGA_MED_V2) {
+		address &= 0xF;
+		if (!address) {
+			//not sure if this is correct, possible byte sized writes are always rejected to $A130F0
+			write_bank_reg_w(address, context, value << 8 | value);
+		} else if (address > 2 && (address & 1)) {
+			write_bank_reg_w(address, context, value);
+		}
+	} else if (address & 1) {
 		write_bank_reg_w(address, context, value);
 	}
 	return context;
@@ -136,13 +270,23 @@
 
 void sega_mapper_serialize(genesis_context *gen, serialize_buffer *buf)
 {
-	save_buffer8(buf, gen->bank_regs, sizeof(gen->bank_regs));
+	save_buffer8(buf, gen->bank_regs, gen->mapper_type == MAPPER_SEGA_MED_V2 ? sizeof(gen->bank_regs) : sizeof(gen->bank_regs) - 1);
 }
 
 void sega_mapper_deserialize(deserialize_buffer *buf, genesis_context *gen)
 {
-	for (int i = 0; i < sizeof(gen->bank_regs); i++)
-	{
-		write_bank_reg_w(i * 2, gen->m68k, load_int8(buf));
+	if (gen->mapper_type == MAPPER_SEGA_MED_V2) {
+		uint16_t reg0 = load_int8(buf);
+		for (int i = 1; i < sizeof(gen->bank_regs) - 1; i++)
+		{
+			write_bank_reg_w(i * 2, gen->m68k, load_int8(buf));
+		}
+		reg0 |= load_int8(buf) << 8;
+		write_bank_reg_w(0, gen->m68k, reg0);
+	} else {
+		for (int i = 0; i < sizeof(gen->bank_regs) - 1; i++)
+		{
+			write_bank_reg_w(i * 2, gen->m68k, load_int8(buf));
+		}
 	}
 }
--- a/sega_mapper.h	Fri Dec 31 13:03:21 2021 -0800
+++ b/sega_mapper.h	Sat Jan 01 18:54:46 2022 -0800
@@ -8,6 +8,22 @@
 m68k_context * write_sram_area_b(uint32_t address, m68k_context * context, uint8_t value);
 m68k_context * write_bank_reg_w(uint32_t address, m68k_context * context, uint16_t value);
 m68k_context * write_bank_reg_b(uint32_t address, m68k_context * context, uint8_t value);
+void* write_med_ram0_w(uint32_t address, void *vcontext, uint16_t value);
+void* write_med_ram1_w(uint32_t address, void *vcontext, uint16_t value);
+void* write_med_ram2_w(uint32_t address, void *vcontext, uint16_t value);
+void* write_med_ram3_w(uint32_t address, void *vcontext, uint16_t value);
+void* write_med_ram4_w(uint32_t address, void *vcontext, uint16_t value);
+void* write_med_ram5_w(uint32_t address, void *vcontext, uint16_t value);
+void* write_med_ram6_w(uint32_t address, void *vcontext, uint16_t value);
+void* write_med_ram7_w(uint32_t address, void *vcontext, uint16_t value);
+void* write_med_ram0_b(uint32_t address, void *vcontext, uint8_t value);
+void* write_med_ram1_b(uint32_t address, void *vcontext, uint8_t value);
+void* write_med_ram2_b(uint32_t address, void *vcontext, uint8_t value);
+void* write_med_ram3_b(uint32_t address, void *vcontext, uint8_t value);
+void* write_med_ram4_b(uint32_t address, void *vcontext, uint8_t value);
+void* write_med_ram5_b(uint32_t address, void *vcontext, uint8_t value);
+void* write_med_ram6_b(uint32_t address, void *vcontext, uint8_t value);
+void* write_med_ram7_b(uint32_t address, void *vcontext, uint8_t value);
 void sega_mapper_serialize(genesis_context *gen, serialize_buffer *buf);
 void sega_mapper_deserialize(deserialize_buffer *buf, genesis_context *gen);