changeset 766:1b2f8280ba81

WIP changes to support reading cart memory map from ROM DB
author Michael Pavone <pavone@retrodev.com>
date Sun, 05 Jul 2015 14:21:34 -0700
parents dc54387ee1cd
children ea525f600b1d
files blastem.c blastem.h config.c io.c render_sdl.c romdb.c romdb.h tern.c tern.h
diffstat 9 files changed, 436 insertions(+), 237 deletions(-) [+]
line wrap: on
line diff
--- a/blastem.c	Thu Jul 02 20:43:01 2015 -0700
+++ b/blastem.c	Sun Jul 05 14:21:34 2015 -0700
@@ -62,6 +62,7 @@
 	fseek(f, SMD_HEADER_SIZE, SEEK_SET);
 
 	uint16_t * dst = cart;
+	int rom_size = filesize;
 	while (filesize > 0) {
 		fread(block, 1, SMD_BLOCK_SIZE, f);
 		for (uint8_t *low = block, *high = (block+SMD_BLOCK_SIZE/2), *end = block+SMD_BLOCK_SIZE; high < end; high++, low++) {
@@ -69,7 +70,7 @@
 		}
 		filesize -= SMD_BLOCK_SIZE;
 	}
-	return 1;
+	return filesize;
 }
 
 void byteswap_rom()
@@ -112,7 +113,7 @@
 	}
 	fread(cart, 2, filesize/2, f);
 	fclose(f);
-	return 1;
+	return filesize;
 }
 
 uint16_t read_dma_value(uint32_t address)
@@ -766,127 +767,6 @@
 	return context;
 }
 
-uint16_t read_sram_w(uint32_t address, m68k_context * context)
-{
-	genesis_context * gen = context->system;
-	address &= gen->save_ram_mask;
-	switch(gen->save_flags)
-	{
-	case RAM_FLAG_BOTH:
-		return gen->save_ram[address] << 8 | gen->save_ram[address+1];
-	case RAM_FLAG_EVEN:
-		return gen->save_ram[address >> 1] << 8 | 0xFF;
-	case RAM_FLAG_ODD:
-		return gen->save_ram[address >> 1] | 0xFF00;
-	}
-	return 0xFFFF;//We should never get here
-}
-
-uint8_t read_sram_b(uint32_t address, m68k_context * context)
-{
-	genesis_context * gen = context->system;
-	address &= gen->save_ram_mask;
-	switch(gen->save_flags)
-	{
-	case RAM_FLAG_BOTH:
-		return gen->save_ram[address];
-	case RAM_FLAG_EVEN:
-		if (address & 1) {
-			return 0xFF;
-		} else {
-			return gen->save_ram[address >> 1];
-		}
-	case RAM_FLAG_ODD:
-		if (address & 1) {
-			return gen->save_ram[address >> 1];
-		} else {
-			return 0xFF;
-		}
-	}
-	return 0xFF;//We should never get here
-}
-
-m68k_context * write_sram_area_w(uint32_t address, m68k_context * context, uint16_t value)
-{
-	genesis_context * gen = context->system;
-	if ((gen->bank_regs[0] & 0x3) == 1) {
-		address &= gen->save_ram_mask;
-		switch(gen->save_flags)
-		{
-		case RAM_FLAG_BOTH:
-			gen->save_ram[address] = value >> 8;
-			gen->save_ram[address+1] = value;
-			break;
-		case RAM_FLAG_EVEN:
-			gen->save_ram[address >> 1] = value >> 8;
-			break;
-		case RAM_FLAG_ODD:
-			gen->save_ram[address >> 1] = value;
-			break;
-		}
-	}
-	return context;
-}
-
-m68k_context * write_sram_area_b(uint32_t address, m68k_context * context, uint8_t value)
-{
-	genesis_context * gen = context->system;
-	if ((gen->bank_regs[0] & 0x3) == 1) {
-		address &= gen->save_ram_mask;
-		switch(gen->save_flags)
-		{
-		case RAM_FLAG_BOTH:
-			gen->save_ram[address] = value;
-			break;
-		case RAM_FLAG_EVEN:
-			if (!(address & 1)) {
-				gen->save_ram[address >> 1] = value;
-			}
-			break;
-		case RAM_FLAG_ODD:
-			if (address & 1) {
-				gen->save_ram[address >> 1] = value;
-			}
-			break;
-		}
-	}
-	return context;
-}
-
-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) {
-			context->mem_pointers[2] = NULL;
-		} else {
-			context->mem_pointers[2] = cart + 0x200000/2;
-		}
-	}
-	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;
-		address &= 0xE;
-		address >>= 1;
-		gen->bank_regs[address] = value;
-		if (!address) {
-			if (value & 1) {
-				context->mem_pointers[2] = NULL;
-			} else {
-				context->mem_pointers[2] = cart + 0x200000/2;
-			}
-		}
-	}
-	return context;
-}
-
 void set_speed_percent(genesis_context * context, uint32_t percent)
 {
 	uint32_t old_clock = context->master_clock;
@@ -898,13 +778,7 @@
 	psg_adjust_master_clock(context->psg, context->master_clock);
 }
 
-#define ROM_END   0x1A4
-#define RAM_ID    0x1B0
-#define RAM_FLAGS 0x1B2
-#define RAM_START 0x1B4
-#define RAM_END   0x1B8
 #define MAX_MAP_CHUNKS (4+7+1)
-#define RAM_FLAG_MASK 0x1800
 
 const memmap_chunk static_map[] = {
 		{0,        0x400000,  0xFFFFFF, 0, MMAP_READ,                          cart,
@@ -947,6 +821,7 @@
 	//TODO: Handle carts larger than 4MB
 	//TODO: Handle non-standard mappers
 	uint32_t size;
+	/*
 	if ((cart[RAM_ID/2] & 0xFF) == 'A' && (cart[RAM_ID/2] >> 8) == 'R') {
 		//Cart has save RAM
 		uint32_t rom_end = ((cart[ROM_END/2] << 16) | cart[ROM_END/2+1]) + 1;
@@ -1020,6 +895,7 @@
 		memcpy(memmap, static_map, sizeof(static_map));
 		num_chunks = sizeof(static_map)/sizeof(memmap_chunk);
 	}
+	*/
 	if (gen->save_ram) {
 		memset(gen->save_ram, 0, size);
 		FILE * f = fopen(sram_filename, "rb");
@@ -1126,6 +1002,7 @@
 	char * romfname = NULL;
 	FILE *address_log = NULL;
 	char * statefile = NULL;
+	int rom_size;
 	uint8_t * debuggerfun = NULL;
 	uint8_t fullscreen = 0, use_gl = 1;
 	for (int i = 1; i < argc; i++) {
@@ -1207,7 +1084,7 @@
 				return 1;
 			}
 		} else if (!loaded) {
-			if(!load_rom(argv[i])) {
+			if(rom_size = load_rom(argv[i])) {
 				fprintf(stderr, "Failed to open %s for reading\n", argv[i]);
 				return 1;
 			}
@@ -1224,12 +1101,12 @@
 		return 1;
 	}
 	tern_node *rom_db = load_rom_db();
-	rom_info info = configure_rom(rom_db, cart);
+	rom_info info = configure_rom(rom_db, cart, rom_size, static_map+1, sizeof(static_map)/sizeof(static_map[0]) - 1);
 	byteswap_rom();
 	set_region(&info, force_version);
 	update_title(info.name);
 	int def_width = 0;
-	char *config_width = tern_find_ptr(config, "videowidth");
+	char *config_width = tern_find_path(config, "video\0width\0").ptrval;
 	if (config_width) {
 		def_width = atoi(config_width);
 	}
@@ -1252,7 +1129,7 @@
 
 	init_vdp_context(&v_context, version_reg & 0x40);
 	gen.frame_end = vdp_cycles_to_frame_end(&v_context);
-	char * config_cycles = tern_find_ptr(config, "clocksmax_cycles");
+	char * config_cycles = tern_find_path(config, "clocks\0max_cycles\0").ptrval;
 	gen.max_cycles = config_cycles ? atoi(config_cycles) : 10000000;
 
 	ym2612_context y_context;
--- a/blastem.h	Thu Jul 02 20:43:01 2015 -0700
+++ b/blastem.h	Sun Jul 05 14:21:34 2015 -0700
@@ -15,10 +15,6 @@
 #include "io.h"
 #include "config.h"
 
-#define RAM_FLAG_ODD  0x1800
-#define RAM_FLAG_EVEN 0x1000
-#define RAM_FLAG_BOTH 0x0000
-
 typedef struct {
 	m68k_context   *m68k;
 	z80_context    *z80;
--- a/config.c	Thu Jul 02 20:43:01 2015 -0700
+++ b/config.c	Sun Jul 05 14:21:34 2015 -0700
@@ -9,8 +9,6 @@
 #include <stdlib.h>
 #include <string.h>
 
-#define MAX_NEST 30 //way more than I'll ever need
-
 #ifdef _WIN32
 char * strtok_r(char * input, char * sep, char ** state)
 {
@@ -32,69 +30,59 @@
 }
 #endif
 
-tern_node * parse_config(char * config_data)
+tern_node * parse_config_int(char **state, int started, int *line)
 {
-	char *state, *curline;
-	char *prefix = NULL;
-	int nest_level = 0;
-	char * prefix_parts[MAX_NEST];
-	int line = 1;
+	char *config_data, *curline;
 	tern_node * head = NULL;
-	while ((curline = strtok_r(config_data, "\n", &state)))
+	config_data = started ? NULL : *state;
+	while ((curline = strtok_r(config_data, "\n", state)))
 	{
+		
 		config_data = NULL;
 		curline = strip_ws(curline);
 		int len = strlen(curline);
 		if (!len) {
+			*line++;
 			continue;
 		}
 		if (curline[0] == '#') {
+			*line++;
 			continue;
 		}
 		if (curline[0] == '}') {
-			if (!nest_level) {
-				fprintf(stderr, "unexpected } on line %d\n", line);
-				exit(1);
+			if (started) {
+				return head;
 			}
-			if (prefix) {
-				free(prefix);
-				prefix = NULL;
-			}
-			nest_level--;
-			curline = strip_ws(curline+1);
+			fprintf(stderr, "unexpected } on line %d\n", *line);
+			exit(1);
 		}
+		
 		char * end = curline + len - 1;
 		if (*end == '{') {
 			*end = 0;
 			curline = strip_ws(curline);
-			prefix_parts[nest_level++] = curline;
-			if (prefix) {
-				free(prefix);
-				prefix = NULL;
-			}
+			*line++;
+			head = tern_insert_node(head, curline, parse_config_int(state, 1, line));
 		} else {
-			if (nest_level && !prefix) {
-				prefix = alloc_concat_m(nest_level, prefix_parts);
-			}
 			char * val = strip_ws(split_keyval(curline));
 			char * key = curline;
-			if (*key) {
-				if (prefix) {
-					key = alloc_concat(prefix, key);
-				}
+			if (*val) {
 				head = tern_insert_ptr(head, key, strdup(val));
-				if (prefix) {
-					free(key);
-				}
+			} else {
+				fprintf(stderr, "Key %s is missing a value on line %d\n", key, *line);
 			}
+			*line++;
 		}
 	}
-	if (prefix) {
-		free(prefix);
-	}
 	return head;
 }
 
+tern_node * parse_config(char * config_data)
+{
+	int line = 1;
+	return parse_config_int(&config_data, 0, &line);
+}
+
 tern_node * parse_config_file(char * config_path)
 {
 	tern_node * ret = NULL;
--- a/io.c	Thu Jul 02 20:43:01 2015 -0700
+++ b/io.c	Sun Jul 05 14:21:34 2015 -0700
@@ -536,7 +536,7 @@
 
 void setup_io_devices(tern_node * config, io_port * ports)
 {
-	tern_node *io_nodes = tern_find_prefix(config, "iodevices");
+	tern_node *io_nodes = tern_get_node(tern_find_path(config, "io\0devices\0"));
 	char * io_1 = tern_find_ptr(io_nodes, "1");
 	char * io_2 = tern_find_ptr(io_nodes, "2");
 	char * io_ext = tern_find_ptr(io_nodes, "ext");
@@ -550,7 +550,7 @@
 #ifndef _WIN32
 		if (ports[i].device_type == IO_SEGA_PARALLEL)
 		{
-			char *pipe_name = tern_find_ptr(config, "ioparallel_pipe");
+			char *pipe_name = tern_find_path(config, "io\0parallel_pipe\0").ptrval;
 			if (!pipe_name)
 			{
 				fprintf(stderr, "IO port %s is configured to use the sega parallel board, but no paralell_pipe is set!\n", io_name(i));
@@ -576,7 +576,7 @@
 				}
 			}
 		} else if (ports[i].device_type == IO_GENERIC) {
-			char *sock_name = tern_find_ptr(config, "iosocket");
+			char *sock_name = tern_find_path(config, "io\0socket\0").ptrval;
 			if (!sock_name)
 			{
 				fprintf(stderr, "IO port %s is configured to use generic IO, but no socket is set!\n", io_name(i));
@@ -665,64 +665,73 @@
 	padbuttons = tern_insert_int(padbuttons, ".start", BUTTON_START);
 	padbuttons = tern_insert_int(padbuttons, ".mode", BUTTON_MODE);
 
-	tern_node * keys = tern_find_prefix(config, "bindingskeys");
+	tern_node * keys = tern_get_node(tern_find_path(config, "bindings\0keys\0"));
 	process_keys(keys, special, padbuttons, NULL);
-	char prefix[] = "bindingspads00";
-	for (int i = 0; i < 100 && i < render_num_joysticks(); i++)
-	{
-		if (i < 10) {
-			prefix[strlen("bindingspads")] = i + '0';
-			prefix[strlen("bindingspads")+1] = 0;
-		} else {
-			prefix[strlen("bindingspads")] = i/10 + '0';
-			prefix[strlen("bindingspads")+1] = i%10 + '0';
-		}
-		tern_node * pad = tern_find_prefix(config, prefix);
-		if (pad) {
-			char dprefix[] = "dpads0";
-			for (int dpad = 0; dpad < 10 && dpad < render_joystick_num_hats(i); dpad++)
-			{
-				dprefix[strlen("dpads")] = dpad + '0';
-				tern_node * pad_dpad = tern_find_prefix(pad, dprefix);
-				char * dirs[] = {"up", "down", "left", "right"};
-				int dirnums[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT};
-				for (int dir = 0; dir < sizeof(dirs)/sizeof(dirs[0]); dir++) {
-					char * target = tern_find_ptr(pad_dpad, dirs[dir]);
-					if (target) {
-						int ui_func, padnum, button;
-						int bindtype = parse_binding_target(target, padbuttons, &ui_func, &padnum, &button);
-						if (bindtype == 1) {
-							bind_dpad_gamepad(i, dpad, dirnums[dir], padnum, button);
-						} else if (bindtype == 2) {
-							bind_dpad_ui(i, dpad, dirnums[dir], ui_func, button);
+	char numstr[] = "00";
+	tern_node * pads = tern_get_node(tern_find_path(config, "bindings\0pads\0"));
+	if (pads) {
+		for (int i = 0; i < 100 && i < render_num_joysticks(); i++)
+		{
+		
+			if (i < 10) {
+				numstr[0] = i + '0';
+				numstr[1] = 0;
+			} else {
+				numstr[0] = i/10 + '0';
+				numstr[1] = i%10 + '0';
+			}
+			tern_node * pad = tern_find_ptr(pads, numstr);
+			if (pad) {
+				tern_node * dpad_node = tern_find_ptr(pad, "dpads");
+				if (dpad_node) {
+					for (int dpad = 0; dpad < 10 && dpad < render_joystick_num_hats(i); dpad++)
+					{
+						numstr[0] = dpad + '0';
+						numstr[1] = 0;
+						tern_node * pad_dpad = tern_find_ptr(dpad_node, numstr);
+						char * dirs[] = {"up", "down", "left", "right"};
+						int dirnums[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT};
+						for (int dir = 0; dir < sizeof(dirs)/sizeof(dirs[0]); dir++) {
+							char * target = tern_find_ptr(pad_dpad, dirs[dir]);
+							if (target) {
+								int ui_func, padnum, button;
+								int bindtype = parse_binding_target(target, padbuttons, &ui_func, &padnum, &button);
+								if (bindtype == 1) {
+									bind_dpad_gamepad(i, dpad, dirnums[dir], padnum, button);
+								} else if (bindtype == 2) {
+									bind_dpad_ui(i, dpad, dirnums[dir], ui_func, button);
+								}
+							}
 						}
 					}
 				}
-			}
-			char bprefix[] = "buttons00";
-			for (int but = 0; but < 100 && but < render_joystick_num_buttons(i); but++)
-			{
-				if (but < 10) {
-					bprefix[strlen("buttons")] = but + '0';
-					bprefix[strlen("buttons")+1] = 0;
-				} else {
-					bprefix[strlen("buttons")] = but/10 + '0';
-					bprefix[strlen("buttons")+1] = but%10 + '0';
-				}
-				char * target = tern_find_ptr(pad, bprefix);
-				if (target) {
-					int ui_func, padnum, button;
-					int bindtype = parse_binding_target(target, padbuttons, &ui_func, &padnum, &button);
-					if (bindtype == 1) {
-						bind_button_gamepad(i, but, padnum, button);
-					} else if (bindtype == 2) {
-						bind_button_ui(i, but, ui_func, button);
+				tern_node *button_node = tern_find_ptr(pad, "buttons");
+				if (button_node) {
+					for (int but = 0; but < 100 && but < render_joystick_num_buttons(i); but++)
+					{
+						if (but < 10) {
+							numstr[0] = but + '0';
+							numstr[1] = 0;
+						} else {
+							numstr[0] = but/10 + '0';
+							numstr[1] = but%10 + '0';
+						}
+						char * target = tern_find_ptr(button_node, numstr);
+						if (target) {
+							int ui_func, padnum, button;
+							int bindtype = parse_binding_target(target, padbuttons, &ui_func, &padnum, &button);
+							if (bindtype == 1) {
+								bind_button_gamepad(i, but, padnum, button);
+							} else if (bindtype == 2) {
+								bind_button_ui(i, but, ui_func, button);
+							}
+						}
 					}
 				}
 			}
 		}
 	}
-	tern_node * speed_nodes = tern_find_prefix(config, "clocksspeeds");
+	tern_node * speed_nodes = tern_get_node(tern_find_path(config, "clocks\0speeds\0"));
 	speeds = malloc(sizeof(uint32_t));
 	speeds[0] = 100;
 	process_speeds(speed_nodes, NULL);
--- a/render_sdl.c	Thu Jul 02 20:43:01 2015 -0700
+++ b/render_sdl.c	Sun Jul 05 14:21:34 2015 -0700
@@ -173,8 +173,10 @@
 	glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data, GL_STATIC_DRAW);
 	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]);
 	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(element_data), element_data, GL_STATIC_DRAW);
-	vshader = load_shader(tern_find_ptr_default(config, "videovertex_shader", "default.v.glsl"), GL_VERTEX_SHADER);
-	fshader = load_shader(tern_find_ptr_default(config, "videofragment_shader", "default.f.glsl"), GL_FRAGMENT_SHADER);
+	tern_val def = {.ptrval = "default.v.glsl"};
+	vshader = load_shader(tern_find_path_default(config, "video\0vertex_shader\0", def).ptrval, GL_VERTEX_SHADER);
+	def.ptrval = "default.f.glsl";
+	fshader = load_shader(tern_find_path_default(config, "video\0fragment_shader\0", def).ptrval, GL_FRAGMENT_SHADER);
 	program = glCreateProgram();
 	glAttachShader(program, vshader);
 	glAttachShader(program, fshader);
@@ -239,7 +241,8 @@
 		exit(1);
 	}
 	float aspect = (float)width / height;
-	if (fabs(aspect - 4.0/3.0) > 0.01 && strcmp(tern_find_ptr_default(config, "videoaspect", "normal"), "stretch")) {
+	tern_val def = {.ptrval = "normal"};
+	if (fabs(aspect - 4.0/3.0) > 0.01 && strcmp(tern_find_path_default(config, "video\0aspect\0", def).ptrval, "stretch")) {
 		for (int i = 0; i < 4; i++)
 		{
 			if (aspect > 4.0/3.0) {
@@ -272,7 +275,7 @@
 	audio_ready = SDL_CreateCond();
 
 	SDL_AudioSpec desired, actual;
-    char * rate_str = tern_find_ptr(config, "audiorate");
+    char * rate_str = tern_find_path(config, "audio\0rate\0").ptrval;
    	int rate = rate_str ? atoi(rate_str) : 0;
    	if (!rate) {
    		rate = 48000;
@@ -280,7 +283,7 @@
     desired.freq = rate;
 	desired.format = AUDIO_S16SYS;
 	desired.channels = 2;
-    char * samples_str = tern_find_ptr(config, "audiobuffer");
+    char * samples_str = tern_find_path(config, "audio\0buffer\0").ptrval;
    	int samples = samples_str ? atoi(samples_str) : 0;
    	if (!samples) {
    		samples = 512;
--- a/romdb.c	Thu Jul 02 20:43:01 2015 -0700
+++ b/romdb.c	Sun Jul 05 14:21:34 2015 -0700
@@ -3,13 +3,140 @@
 #include "config.h"
 #include "romdb.h"
 #include "util.h"
+#include "blastem.h"
 
 #define TITLE_START 0x150
 #define TITLE_END (TITLE_START+48)
 #define GAME_ID_OFF 0x183
 #define GAME_ID_LEN 8
+#define ROM_END   0x1A4
+#define RAM_ID    0x1B0
+#define RAM_FLAGS 0x1B2
+#define RAM_START 0x1B4
+#define RAM_END   0x1B8
 #define REGION_START 0x1F0
 
+uint16_t read_sram_w(uint32_t address, m68k_context * context)
+{
+	genesis_context * gen = context->system;
+	address &= gen->save_ram_mask;
+	switch(gen->save_flags)
+	{
+	case RAM_FLAG_BOTH:
+		return gen->save_ram[address] << 8 | gen->save_ram[address+1];
+	case RAM_FLAG_EVEN:
+		return gen->save_ram[address >> 1] << 8 | 0xFF;
+	case RAM_FLAG_ODD:
+		return gen->save_ram[address >> 1] | 0xFF00;
+	}
+	return 0xFFFF;//We should never get here
+}
+
+uint8_t read_sram_b(uint32_t address, m68k_context * context)
+{
+	genesis_context * gen = context->system;
+	address &= gen->save_ram_mask;
+	switch(gen->save_flags)
+	{
+	case RAM_FLAG_BOTH:
+		return gen->save_ram[address];
+	case RAM_FLAG_EVEN:
+		if (address & 1) {
+			return 0xFF;
+		} else {
+			return gen->save_ram[address >> 1];
+		}
+	case RAM_FLAG_ODD:
+		if (address & 1) {
+			return gen->save_ram[address >> 1];
+		} else {
+			return 0xFF;
+		}
+	}
+	return 0xFF;//We should never get here
+}
+
+m68k_context * write_sram_area_w(uint32_t address, m68k_context * context, uint16_t value)
+{
+	genesis_context * gen = context->system;
+	if ((gen->bank_regs[0] & 0x3) == 1) {
+		address &= gen->save_ram_mask;
+		switch(gen->save_flags)
+		{
+		case RAM_FLAG_BOTH:
+			gen->save_ram[address] = value >> 8;
+			gen->save_ram[address+1] = value;
+			break;
+		case RAM_FLAG_EVEN:
+			gen->save_ram[address >> 1] = value >> 8;
+			break;
+		case RAM_FLAG_ODD:
+			gen->save_ram[address >> 1] = value;
+			break;
+		}
+	}
+	return context;
+}
+
+m68k_context * write_sram_area_b(uint32_t address, m68k_context * context, uint8_t value)
+{
+	genesis_context * gen = context->system;
+	if ((gen->bank_regs[0] & 0x3) == 1) {
+		address &= gen->save_ram_mask;
+		switch(gen->save_flags)
+		{
+		case RAM_FLAG_BOTH:
+			gen->save_ram[address] = value;
+			break;
+		case RAM_FLAG_EVEN:
+			if (!(address & 1)) {
+				gen->save_ram[address >> 1] = value;
+			}
+			break;
+		case RAM_FLAG_ODD:
+			if (address & 1) {
+				gen->save_ram[address >> 1] = value;
+			}
+			break;
+		}
+	}
+	return context;
+}
+
+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) {
+			context->mem_pointers[2] = NULL;
+		} else {
+			context->mem_pointers[2] = cart + 0x200000/2;
+		}
+	}
+	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;
+		address &= 0xE;
+		address >>= 1;
+		gen->bank_regs[address] = value;
+		if (!address) {
+			if (value & 1) {
+				context->mem_pointers[2] = NULL;
+			} else {
+				context->mem_pointers[2] = cart + 0x200000/2;
+			}
+		}
+	}
+	return context;
+}
+
 tern_node *load_rom_db()
 {
 	char *exe_dir = get_exe_dir();
@@ -77,16 +204,96 @@
 	return regions;
 }
 
+uint32_t get_u32be(uint8_t *data)
+{
+	return *data << 24 | data[1] << 16 | data[2] << 8 | data[3];
+}
 
-rom_info configure_rom_heuristics(uint8_t *rom)
+void add_memmap_header(rom_info *info, uint8_t *rom, uint32_t size, memmap_chunk const *base_map, int base_chunks)
+{
+	if (rom[RAM_ID] == 'R' && rom[RAM_ID+1] == 'A') {
+		uint32_t rom_end = get_u32be(rom + ROM_END) + 1;
+		uint32_t ram_start = get_u32be(rom + RAM_START);
+		uint32_t ram_end = get_u32be(rom + RAM_END);
+		uint32_t ram_flags = info->save_type = rom[RAM_FLAGS] & RAM_FLAG_MASK;
+		ram_start &= 0xFFFFFE;
+		ram_end |= 1;
+		
+		info->map_chunks = base_chunks + (ram_start >= rom_end ? 2 : 3);
+		info->map = malloc(sizeof(memmap_chunk) * info->map_chunks);
+		memset(info->map, 0, sizeof(memmap_chunk)*2);
+		memcpy(info->map+2, base_map, sizeof(memmap_chunk) * base_chunks);
+		
+		if (ram_start >= rom_end) {
+			info->map[0].end = rom_end;
+			//TODO: ROM mirroring
+			info->map[0].mask = 0xFFFFFF;
+			info->map[0].flags = MMAP_READ;
+			info->map[0].buffer = rom;
+			
+			info->map[1].start = ram_start;
+			info->map[1].mask = ram_end - ram_start;
+			info->map[1].end = ram_end + 1;
+			info->map[1].flags = MMAP_READ | MMAP_WRITE;
+			uint32_t size = info->map[1].mask + 1;
+			if (ram_flags == RAM_FLAG_ODD) {
+				info->map[1].flags |= MMAP_ONLY_ODD;
+				size /= 2;
+			} else if (ram_flags == RAM_FLAG_EVEN) {
+				info->map[1].flags |= MMAP_ONLY_EVEN;
+				size /= 2;
+			}
+			info->map[1].buffer = malloc(size);
+		} else {
+			//Assume the standard Sega mapper
+			info->map[0].end = 0x200000;
+			info->map[0].mask = 0xFFFFFF;
+			info->map[0].flags = MMAP_READ;
+			info->map[0].buffer = rom;
+			
+			info->map[1].start = 0x200000;
+			info->map[1].end = 0x400000;
+			info->map[1].mask = 0x1FFFFF;
+			info->map[1].flags = MMAP_READ | MMAP_PTR_IDX | MMAP_FUNC_NULL;
+			info->map[1].ptr_index = 2;
+			info->map[1].read_16 = (read_16_fun)read_sram_w;//these will only be called when mem_pointers[2] == NULL
+			info->map[1].read_8 = (read_8_fun)read_sram_b;
+			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 = cart + 0x200000;
+			
+			memmap_chunk *last = info->map + info->map_chunks - 1;
+			memset(last, 0, sizeof(memmap_chunk));
+			last->start = 0xA13000;
+			last->end = 0xA13100;
+			last->mask = 0xFF;
+			last->write_16 = (write_16_fun)write_bank_reg_w;
+			last->write_8 = (write_8_fun)write_bank_reg_b;
+		}
+	} else {
+		info->map_chunks = base_chunks + 1;
+		info->map = malloc(sizeof(memmap_chunk) * info->map_chunks);
+		memset(info->map, 0, sizeof(memmap_chunk));
+		memcpy(info->map+1, base_map, sizeof(memmap_chunk) * base_chunks);
+		
+		info->map[0].end = 0x400000;
+		info->map[0].mask = 0xFFFFFF;
+		info->map[0].flags = MMAP_READ;
+		info->map[0].buffer = rom;
+		info->save_type = SAVE_NONE;
+	}
+}
+
+rom_info configure_rom_heuristics(uint8_t *rom, uint32_t rom_size, memmap_chunk const *base_map, uint32_t base_chunks)
 {
 	rom_info info;
 	info.name = get_header_name(rom);
 	info.regions = get_header_regions(rom);
+	add_memmap_header(&info, rom, rom_size, base_map, base_chunks);
 	return info;
 }
 
-rom_info configure_rom(tern_node *rom_db, void *vrom)
+rom_info configure_rom(tern_node *rom_db, void *vrom, uint32_t rom_size, memmap_chunk const *base_map, uint32_t base_chunks)
 {
 	uint8_t product_id[GAME_ID_LEN+1];
 	uint8_t *rom = vrom;
@@ -102,7 +309,7 @@
 	}
 	tern_node * entry = tern_find_prefix(rom_db, product_id);
 	if (!entry) {
-		return configure_rom_heuristics(rom);
+		return configure_rom_heuristics(rom, rom_size, base_map, base_chunks);
 	}
 	rom_info info;
 	info.name = tern_find_ptr(entry, "name");
@@ -123,5 +330,18 @@
 	if (!info.regions) {
 		info.regions = get_header_regions(rom);
 	}
+	
+	tern_node *map = tern_find_prefix(entry, "map");
+	if (map) {
+		uint32_t map_count = tern_count(map);
+		if (map_count) {
+			
+		} else {
+			add_memmap_header(&info, rom, rom_size, base_map, base_chunks);
+		}
+	} else {
+		add_memmap_header(&info, rom, rom_size, base_map, base_chunks);
+	}
+	
 	return info;
 }
--- a/romdb.h	Thu Jul 02 20:43:01 2015 -0700
+++ b/romdb.h	Sun Jul 05 14:21:34 2015 -0700
@@ -5,17 +5,29 @@
 #define REGION_U 2
 #define REGION_E 4
 
+#define RAM_FLAG_ODD  0x18
+#define RAM_FLAG_EVEN 0x10
+#define RAM_FLAG_BOTH 0x00
+#define RAM_FLAG_MASK RAM_FLAG_ODD
+#define SAVE_I2C      0x01
+#define SAVE_NONE     0xFF
+
 #include "tern.h"
 #include "backend.h"
 
 typedef struct {
-	char         *name;
-	memmap_chunk *map;
-	uint8_t      regions;
+	char          *name;
+	memmap_chunk  *map;
+	uint8_t       *save_buffer;
+	uint32_t      map_chunks;
+	uint32_t      save_size;
+	uint8_t       save_type;
+	uint8_t       regions;
 } rom_info;
 
 tern_node *load_rom_db();
-rom_info configure_rom(tern_node *rom_db, void *vrom);
+rom_info configure_rom(tern_node *rom_db, void *vrom, uint32_t rom_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);
 
 #endif //ROMDB_H_
--- a/tern.c	Thu Jul 02 20:43:01 2015 -0700
+++ b/tern.c	Sun Jul 05 14:21:34 2015 -0700
@@ -6,6 +6,8 @@
 #include "tern.h"
 #include <stddef.h>
 #include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
 
 tern_node * tern_insert(tern_node * head, char * key, tern_val value)
 {
@@ -115,6 +117,32 @@
 	return tern_find_ptr_default(head, key, NULL);
 }
 
+tern_val tern_find_path_default(tern_node *head, char *key, tern_val def)
+{
+	tern_val ret;
+	while (*key)
+	{
+		if (!tern_find(head, key, &ret)) {
+			return def;
+		}
+		key = key + strlen(key) + 1;
+		if (*key) {
+			head = tern_get_node(ret);
+			if (!head) {
+				return def;
+			}
+		}
+	}
+	return ret;
+}
+
+tern_val tern_find_path(tern_node *head, char *key)
+{
+	tern_val def;
+	def.ptrval = NULL;
+	return tern_find_path_default(head, key, def);
+}
+
 tern_node * tern_insert_ptr(tern_node * head, char * key, void * value)
 {
 	tern_val val;
@@ -122,6 +150,60 @@
 	return tern_insert(head, key, val);
 }
 
+tern_node * tern_insert_node(tern_node *head, char *key, tern_node *value)
+{
+	tern_val val;
+	val.intval = ((intptr_t)value) | 1;
+	return tern_insert(head, key, val);
+}
+
+uint32_t tern_count(tern_node *head)
+{
+	uint32_t count = 0;
+	if (head->left) {
+		count += tern_count(head->left);
+	}
+	if (head->right) {
+		count += tern_count(head->right);
+	}
+	if (!head->el) {
+		count++;
+	} else if (head->straight.next) {
+		count += tern_count(head->straight.next);
+	}
+	return count;
+}
+
+#define MAX_ITER_KEY 127
+void tern_foreach_int(tern_node *head, iter_fun fun, void *data, char *keybuf, int pos)
+{
+	if (!head->el) {
+		keybuf[pos] = 0;
+		fun(keybuf, head->straight.value, data);
+	}
+	if (head->left) {
+		tern_foreach_int(head->left, fun, data, keybuf, pos);
+	}
+	if (head->el) {
+		if (pos == MAX_ITER_KEY) {
+			fputs("exceeded maximum key size", stderr);
+			exit(1);
+		}
+		keybuf[pos] = head->el;
+		tern_foreach_int(head->straight.next, fun, data, keybuf, pos+1);
+	}
+	if (head->right) {
+		tern_foreach_int(head->left, fun, data, keybuf, pos);
+	}
+}
+
+void tern_foreach(tern_node *head, iter_fun fun, void *data)
+{
+	//lame, but good enough for my purposes
+	char key[MAX_ITER_KEY+1];
+	tern_foreach_int(head, fun, data, key, 0);
+}
+
 char * tern_int_key(uint32_t key, char * buf)
 {
 	char * cur = buf;
@@ -133,3 +215,8 @@
 	*cur = 0;
 	return buf;
 }
+
+tern_node * tern_get_node(tern_val value)
+{
+	return value.intval & 1 ? (tern_node *)(value.intval & ~1) : NULL;
+}
--- a/tern.h	Thu Jul 02 20:43:01 2015 -0700
+++ b/tern.h	Sun Jul 05 14:21:34 2015 -0700
@@ -25,6 +25,8 @@
 	char             el;
 } tern_node;
 
+typedef void (*iter_fun)(char *key, tern_val val, void *data);
+
 tern_node * tern_insert(tern_node * head, char * key, tern_val value);
 int tern_find(tern_node * head, char * key, tern_val *ret);
 tern_node * tern_find_prefix(tern_node * head, char * key);
@@ -32,7 +34,12 @@
 tern_node * tern_insert_int(tern_node * head, char * key, intptr_t value);
 void * tern_find_ptr_default(tern_node * head, char * key, void * def);
 void * tern_find_ptr(tern_node * head, char * key);
+tern_val tern_find_path_default(tern_node *head, char *key, tern_val def);
+tern_val tern_find_path(tern_node *head, char *key);
 tern_node * tern_insert_ptr(tern_node * head, char * key, void * value);
+tern_node * tern_insert_node(tern_node *head, char *key, tern_node *value);
+uint32_t tern_count(tern_node *head);
 char * tern_int_key(uint32_t key, char * buf);
+tern_node * tern_get_node(tern_val value);
 
 #endif //TERN_H_