changeset 773:13d3744b170e

Merge
author Michael Pavone <pavone@retrodev.com>
date Sat, 18 Jul 2015 10:42:15 -0700
parents 1b82b282b829 (diff) 2a25ea5d94f7 (current diff)
children 41dc895e85ff
files util.c
diffstat 15 files changed, 1312 insertions(+), 437 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Sun Jun 28 19:23:38 2015 -0700
+++ b/Makefile	Sat Jul 18 10:42:15 2015 -0700
@@ -99,7 +99,7 @@
 AUDIOOBJS=ym2612.o psg.o wave.o
 CONFIGOBJS=config.o tern.o util.o
 
-MAINOBJS=blastem.o debug.o gdb_remote.o vdp.o render_sdl.o io.o $(CONFIGOBJS) gst.o $(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS)
+MAINOBJS=blastem.o debug.o gdb_remote.o vdp.o render_sdl.o io.o romdb.o $(CONFIGOBJS) gst.o $(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS)
 
 ifeq ($(CPU),x86_64)
 CFLAGS+=-DX86_64 -m64
--- a/blastem.c	Sun Jun 28 19:23:38 2015 -0700
+++ b/blastem.c	Sat Jul 18 10:42:15 2015 -0700
@@ -13,6 +13,7 @@
 #include "gdb_remote.h"
 #include "gst.h"
 #include "util.h"
+#include "romdb.h"
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -61,14 +62,23 @@
 	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++) {
-			*(dst++) = *high << 8 | *low;
+			*(dst++) = *low << 8 | *high;
 		}
 		filesize -= SMD_BLOCK_SIZE;
 	}
-	return 1;
+	return filesize;
+}
+
+void byteswap_rom()
+{
+	for(unsigned short * cur = cart; cur - cart < CARTRIDGE_WORDS; ++cur)
+	{
+		*cur = (*cur >> 8) | (*cur << 8);
+	}
 }
 
 int load_rom(char * filename)
@@ -103,12 +113,7 @@
 	}
 	fread(cart, 2, filesize/2, f);
 	fclose(f);
-	for(unsigned short * cur = cart; cur - cart < (filesize/2); ++cur)
-	{
-		*cur = (*cur >> 8) | (*cur << 8);
-	}
-	//TODO: Mirror ROM
-	return 1;
+	return filesize;
 }
 
 uint16_t read_dma_value(uint32_t address)
@@ -762,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;
@@ -894,17 +778,9 @@
 	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,
-		           NULL,          NULL,         NULL,            NULL},
+const memmap_chunk base_map[] = {
 		{0xE00000, 0x1000000, 0xFFFF,   0, MMAP_READ | MMAP_WRITE | MMAP_CODE, ram,
 		           NULL,          NULL,         NULL,            NULL},
 		{0xC00000, 0xE00000,  0x1FFFFF, 0, 0,                                  NULL,
@@ -915,120 +791,49 @@
 		           (read_8_fun)io_read,         (write_8_fun)io_write}
 	};
 
-char * sram_filename;
+char * save_filename;
 genesis_context * genesis;
-void save_sram()
+void persist_save()
 {
-	FILE * f = fopen(sram_filename, "wb");
+	FILE * f = fopen(save_filename, "wb");
 	if (!f) {
-		fprintf(stderr, "Failed to open SRAM file %s for writing\n", sram_filename);
+		fprintf(stderr, "Failed to open %s file %s for writing\n", genesis->save_type == SAVE_I2C ? "EEPROM" : "SRAM", save_filename);
 		return;
 	}
-	uint32_t size = genesis->save_ram_mask+1;
-	if (genesis->save_flags != RAM_FLAG_BOTH) {
-		size/= 2;
-	}
-	fwrite(genesis->save_ram, 1, size, f);
+	fwrite(genesis->save_storage, 1, genesis->save_size, f);
 	fclose(f);
-	printf("Saved SRAM to %s\n", sram_filename);
+	printf("Saved %s to %s\n", genesis->save_type == SAVE_I2C ? "EEPROM" : "SRAM", save_filename);
 }
 
-void init_run_cpu(genesis_context * gen, FILE * address_log, char * statefile, uint8_t * debugger)
+void init_run_cpu(genesis_context * gen, rom_info *rom, FILE * address_log, char * statefile, uint8_t * debugger)
 {
 	m68k_options opts;
-	memmap_chunk memmap[MAX_MAP_CHUNKS];
-	uint32_t num_chunks;
-	void * initial_mapped = NULL;
-	gen->save_ram = NULL;
-	//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;
-		uint32_t ram_start = (cart[RAM_START/2] << 16) | cart[RAM_START/2+1];
-		uint32_t ram_end = (cart[RAM_END/2] << 16) | cart[RAM_END/2+1];
-		uint16_t ram_flags = cart[RAM_FLAGS/2];
-		gen->save_flags = ram_flags & RAM_FLAG_MASK;
-		memset(memmap, 0, sizeof(memmap_chunk)*2);
-		if (ram_start >= rom_end) {
-			memmap[0].end = rom_end;
-			memmap[0].mask = 0xFFFFFF;
-			memmap[0].flags = MMAP_READ;
-			memmap[0].buffer = cart;
-
-			ram_start &= 0xFFFFFE;
-			ram_end |= 1;
-			memmap[1].start = ram_start;
-			gen->save_ram_mask = memmap[1].mask = ram_end-ram_start;
-			ram_end += 1;
-			memmap[1].end = ram_end;
-			memmap[1].flags = MMAP_READ | MMAP_WRITE;
-			size = ram_end-ram_start;
-			if ((ram_flags & RAM_FLAG_MASK) == RAM_FLAG_ODD) {
-				memmap[1].flags |= MMAP_ONLY_ODD;
-				size /= 2;
-			} else if((ram_flags & RAM_FLAG_MASK) == RAM_FLAG_EVEN) {
-				memmap[1].flags |= MMAP_ONLY_EVEN;
-				size /= 2;
+	
+	gen->save_type = rom->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;
+		memset(gen->save_storage, 0, rom->save_size);
+		FILE * f = fopen(save_filename, "rb");
+		if (f) {
+			uint32_t read = fread(gen->save_storage, 1, rom->save_size, f);
+			fclose(f);
+			if (read > 0) {
+				printf("Loaded %s from %s\n", rom->save_type == SAVE_I2C ? "EEPROM" : "SRAM", save_filename);
 			}
-			memmap[1].buffer = gen->save_ram = malloc(size);
-
-			memcpy(memmap+2, static_map+1, sizeof(static_map)-sizeof(static_map[0]));
-			num_chunks = sizeof(static_map)/sizeof(memmap_chunk)+1;
-		} else {
-			//Assume the standard Sega mapper for now
-			memmap[0].end = 0x200000;
-			memmap[0].mask = 0xFFFFFF;
-			memmap[0].flags = MMAP_READ;
-			memmap[0].buffer = cart;
-
-			memmap[1].start = 0x200000;
-			memmap[1].end = 0x400000;
-			memmap[1].mask = 0x1FFFFF;
-			ram_start &= 0xFFFFFE;
-			ram_end |= 1;
-			gen->save_ram_mask = ram_end-ram_start;
-			memmap[1].flags = MMAP_READ | MMAP_PTR_IDX | MMAP_FUNC_NULL;
-			memmap[1].ptr_index = 2;
-			memmap[1].read_16 = (read_16_fun)read_sram_w;//these will only be called when mem_pointers[2] == NULL
-			memmap[1].read_8 = (read_8_fun)read_sram_b;
-			memmap[1].write_16 = (write_16_fun)write_sram_area_w;//these will be called all writes to the area
-			memmap[1].write_8 = (write_8_fun)write_sram_area_b;
-			memcpy(memmap+2, static_map+1, sizeof(static_map)-sizeof(static_map[0]));
-			num_chunks = sizeof(static_map)/sizeof(memmap_chunk)+1;
-			memset(memmap+num_chunks, 0, sizeof(memmap[num_chunks]));
-			memmap[num_chunks].start = 0xA13000;
-			memmap[num_chunks].end = 0xA13100;
-			memmap[num_chunks].mask = 0xFF;
-			memmap[num_chunks].write_16 = (write_16_fun)write_bank_reg_w;
-			memmap[num_chunks].write_8 = (write_8_fun)write_bank_reg_b;
-			num_chunks++;
-			ram_end++;
-			size = ram_end-ram_start;
-			if ((ram_flags & RAM_FLAG_MASK) != RAM_FLAG_BOTH) {
-				size /= 2;
-			}
-			gen->save_ram = malloc(size);
-			memmap[1].buffer = initial_mapped = cart + 0x200000/2;
+		}
+		atexit(persist_save);
+		if (gen->save_type == SAVE_I2C) {
+			eeprom_init(&gen->eeprom, gen->save_storage, gen->save_size);
 		}
 	} else {
-		memcpy(memmap, static_map, sizeof(static_map));
-		num_chunks = sizeof(static_map)/sizeof(memmap_chunk);
+		gen->save_storage = NULL;
 	}
-	if (gen->save_ram) {
-		memset(gen->save_ram, 0, size);
-		FILE * f = fopen(sram_filename, "rb");
-		if (f) {
-			uint32_t read = fread(gen->save_ram, 1, size, f);
-			fclose(f);
-			if (read > 0) {
-				printf("Loaded SRAM from %s\n", sram_filename);
-			}
-		}
-		atexit(save_sram);
-	}
-	init_m68k_opts(&opts, memmap, num_chunks, MCLKS_PER_68K);
+	
+	init_m68k_opts(&opts, rom->map, rom->map_chunks, MCLKS_PER_68K);
 	opts.address_log = address_log;
 	m68k_context *context = init_68k_context(&opts);
 	gen->m68k = context;
@@ -1041,8 +846,8 @@
 	//work RAM
 	context->mem_pointers[1] = ram;
 	//save RAM/map
-	context->mem_pointers[2] = initial_mapped;
-	context->mem_pointers[3] = (uint16_t *)gen->save_ram;
+	context->mem_pointers[2] = rom->map[1].buffer;
+	context->mem_pointers[3] = (uint16_t *)gen->save_storage;
 	
 	if (statefile) {
 		uint32_t pc = load_gst(gen, statefile);
@@ -1065,75 +870,36 @@
 	}
 }
 
-char title[64];
-
-#define TITLE_START 0x150
-#define TITLE_END (TITLE_START+48)
+char *title;
 
-void update_title()
+void update_title(char *rom_name)
 {
-	uint16_t *last = cart + TITLE_END/2 - 1;
-	while(last > cart + TITLE_START/2 && *last == 0x2020)
-	{
-		last--;
+	if (title) {
+		free(title);
+		title = NULL;
 	}
-	uint16_t *start = cart + TITLE_START/2;
-	char *cur = title;
-	char last_char = ' ';
-	for (; start != last; start++)
-	{
-		if ((last_char != ' ' || (*start >> 8) != ' ') && (*start >> 8) < 0x80) {
-			*(cur++) = *start >> 8;
-			last_char = *start >> 8;
-		}
-		if (last_char != ' ' || (*start & 0xFF) != ' ' && (*start & 0xFF) < 0x80) {
-			*(cur++) = *start;
-			last_char = *start & 0xFF;
-		}
-	}
-	*(cur++) = *start >> 8;
-	if ((*start & 0xFF) != ' ') {
-		*(cur++) = *start;
-	}
-	strcpy(cur, " - BlastEm");
+	title = alloc_concat(rom_name, " - BlastEm");
 }
 
-#define REGION_START 0x1F0
-
-int detect_specific_region(char region)
+void set_region(rom_info *info, uint8_t region)
 {
-	return (cart[REGION_START/2] & 0xFF) == region || (cart[REGION_START/2] >> 8) == region || (cart[REGION_START/2+1] & 0xFF) == region;
+	if (!region) {
+		char * def_region = tern_find_ptr(config, "default_region");
+		if (def_region && (!info->regions || (info->regions & translate_region_char(toupper(*def_region))))) {
+			region = translate_region_char(toupper(*def_region));
+		} else {
+			region = info->regions;
+		}
+	}
+	if (region & REGION_E) {
+		version_reg = NO_DISK | EUR;
+	} else if (region & REGION_J) {
+		version_reg = NO_DISK | JAP;
+	} else {
+		version_reg = NO_DISK | USA;
+	}
 }
 
-void detect_region()
-{
-	if (detect_specific_region('U')|| detect_specific_region('B') || detect_specific_region('4')) {
-		version_reg = NO_DISK | USA;
-	} else if (detect_specific_region('J')) {
-		version_reg = NO_DISK | JAP;
-	} else if (detect_specific_region('E') || detect_specific_region('A')) {
-		version_reg = NO_DISK | EUR;
-	} else {
-		char * def_region = tern_find_ptr(config, "default_region");
-		if (def_region) {
-			switch(*def_region)
-			{
-			case 'j':
-			case 'J':
-				version_reg = NO_DISK | JAP;
-				break;
-			case 'u':
-			case 'U':
-				version_reg = NO_DISK | USA;
-				break;
-			case 'e':
-			case 'E':
-				version_reg = NO_DISK | EUR;
-				break;
-			}
-		}
-	}
-}
 #ifndef NO_Z80
 const memmap_chunk z80_map[] = {
 	{ 0x0000, 0x4000,  0x1FFF, 0, MMAP_READ | MMAP_WRITE | MMAP_CODE, z80_ram, NULL, NULL, NULL,              NULL },
@@ -1161,6 +927,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++) {
@@ -1204,21 +971,8 @@
 					fputs("-r must be followed by region (J, U or E)\n", stderr);
 					return 1;
 				}
-				switch (argv[i][0])
-				{
-				case 'j':
-				case 'J':
-					force_version = NO_DISK | JAP;
-					break;
-				case 'u':
-				case 'U':
-					force_version = NO_DISK | USA;
-					break;
-				case 'e':
-				case 'E':
-					force_version = NO_DISK | EUR;
-					break;
-				default:
+				force_version = translate_region_char(toupper(argv[i][0]));
+				if (!force_version) {
 					fprintf(stderr, "'%c' is not a valid region character for the -r option\n", argv[i][0]);
 					return 1;
 				}
@@ -1255,7 +1009,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;
 			}
@@ -1271,14 +1025,13 @@
 		fputs("You must specify a ROM filename!\n", stderr);
 		return 1;
 	}
-	if (force_version) {
-		version_reg = force_version;
-	} else {
-		detect_region();
-	}
-	update_title();
+	tern_node *rom_db = load_rom_db();
+	rom_info info = configure_rom(rom_db, cart, rom_size, base_map, sizeof(base_map)/sizeof(base_map[0]));
+	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);
 	}
@@ -1301,7 +1054,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;
@@ -1330,20 +1083,22 @@
 	setup_io_devices(config, gen.ports);
 
 	int fname_size = strlen(romfname);
-	sram_filename = malloc(fname_size+6);
-	memcpy(sram_filename, romfname, fname_size);
+	char * ext = info.save_type == SAVE_I2C ? "eeprom" : "sram";
+	save_filename = malloc(fname_size+strlen(ext) + 2);
+	memcpy(save_filename, romfname, fname_size);
 	int i;
 	for (i = fname_size-1; fname_size >= 0; --i) {
-		if (sram_filename[i] == '.') {
-			strcpy(sram_filename + i + 1, "sram");
+		if (save_filename[i] == '.') {
+			strcpy(save_filename + i + 1, ext);
 			break;
 		}
 	}
 	if (i < 0) {
-		strcpy(sram_filename + fname_size, ".sram");
+		save_filename[fname_size] = '.';
+		strcpy(save_filename + fname_size + 1, ext);
 	}
 	set_keybindings(gen.ports);
 
-	init_run_cpu(&gen, address_log, statefile, debuggerfun);
+	init_run_cpu(&gen, &info, address_log, statefile, debuggerfun);
 	return 0;
 }
--- a/blastem.h	Sun Jun 28 19:23:38 2015 -0700
+++ b/blastem.h	Sat Jul 18 10:42:15 2015 -0700
@@ -14,10 +14,7 @@
 #include "psg.h"
 #include "io.h"
 #include "config.h"
-
-#define RAM_FLAG_ODD  0x1800
-#define RAM_FLAG_EVEN 0x1000
-#define RAM_FLAG_BOTH 0x0000
+#include "romdb.h"
 
 typedef struct {
 	m68k_context   *m68k;
@@ -25,16 +22,20 @@
 	vdp_context    *vdp;
 	ym2612_context *ym;
 	psg_context    *psg;
-	uint8_t        *save_ram;
+	uint8_t        *save_storage;
+	eeprom_map     *eeprom_map;
+	uint32_t       num_eeprom;
+	uint32_t       save_size;
 	uint32_t       save_ram_mask;
-	uint32_t       save_flags;
 	uint32_t       master_clock; //Current master clock value
 	uint32_t       normal_clock; //Normal master clock (used to restore master clock after turbo mode)
 	uint32_t       frame_end;
 	uint32_t       max_cycles;
 	uint8_t        bank_regs[8];
+	uint8_t        save_type;
 	io_port        ports[3];
 	uint8_t        bus_busy;
+	eeprom_state   eeprom;
 } genesis_context;
 
 extern genesis_context * genesis;
--- a/config.c	Sun Jun 28 19:23:38 2015 -0700
+++ b/config.c	Sat Jul 18 10:42:15 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/config.h	Sun Jun 28 19:23:38 2015 -0700
+++ b/config.h	Sat Jul 18 10:42:15 2015 -0700
@@ -7,6 +7,7 @@
 #define CONFIG_H_
 #include "tern.h"
 
+tern_node * parse_config_file(char * config_path);
 tern_node * load_config();
 
 #endif //CONFIG_H_
--- a/io.c	Sun Jun 28 19:23:38 2015 -0700
+++ b/io.c	Sat Jul 18 10:42:15 2015 -0700
@@ -248,7 +248,7 @@
 		{
 		case UI_DEBUG_MODE_INC:
 			ui_debug_mode++;
-			if (ui_debug_mode == 4) {
+			if (ui_debug_mode == 7) {
 				ui_debug_mode = 0;
 			}
 			genesis->vdp->debug = ui_debug_mode;
@@ -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	Sun Jun 28 19:23:38 2015 -0700
+++ b/render_sdl.c	Sat Jul 18 10:42:15 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;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rom.db	Sat Jul 18 10:42:15 2015 -0700
@@ -0,0 +1,168 @@
+T-081326 {
+	name NBA Jam
+	EEPROM {
+		type i2c
+		size 256
+	}
+	map {
+		0 {
+			device ROM
+			last 1FFFFF
+		}
+		200000 {
+			device EEPROM
+			last 3FFFFF
+			bits_read {
+				1 sda
+			}
+			bits_write {
+				0 sda
+				1 scl
+			}
+		}
+	}
+}
+T-81033 {
+	name NBA Jam
+	EEPROM {
+		type i2c
+		size 256
+	}
+	map {
+		0 {
+			device ROM
+			last 1FFFFF
+		}
+		200000 {
+			device EEPROM
+			last 3FFFFF
+			bits_read {
+				1 sda
+			}
+			bits_write {
+				0 sda
+				1 scl
+			}
+		}
+	}
+}
+T-081276 {
+	name NFL Quarterback Club
+	EEPROM {
+		type i2c
+		size 256
+	}
+	map {
+		0 {
+			device ROM
+			last 1FFFFF
+		}
+		200000 {
+			device EEPROM
+			last 3FFFFF
+			bits_read {
+				0 sda
+			}
+			bits_write {
+				0 sda
+				8 scl
+			}
+		}
+	}
+}
+T-81406 {
+	name NBA Jam TE
+	EEPROM {
+		type i2c
+		size 512
+	}
+	map {
+		0 {
+			device ROM
+			last 1FFFFF
+		}
+		200000 {
+			device EEPROM
+			last 3FFFFF
+			bits_read {
+				0 sda
+			}
+			bits_write {
+				0 sda
+				8 scl
+			}
+		}
+	}
+}
+T-081586 {
+	name NFL Quarterback Club '96
+	EEPROM {
+		type i2c
+		size 2048
+	}
+	map {
+		0 {
+			device ROM
+			last 1FFFFF
+		}
+		200000 {
+			device EEPROM
+			last 3FFFFF
+			bits_read {
+				0 sda
+			}
+			bits_write {
+				0 sda
+				8 scl
+			}
+		}
+	}
+}
+T-81576 {
+	name College Slam
+	EEPROM {
+		type i2c
+		size 8192
+	}
+	map {
+		0 {
+			device ROM
+			last 1FFFFF
+		}
+		200000 {
+			device EEPROM
+			last 3FFFFF
+			bits_read {
+				0 sda
+			}
+			bits_write {
+				0 sda
+				8 scl
+			}
+		}
+	}
+}
+T-81476 {
+	name Frank Thomas Big Hurt Baseball
+	EEPROM {
+		type i2c
+		size 8192
+	}
+	map {
+		0 {
+			device ROM
+			last 1FFFFF
+		}
+		200000 {
+			device EEPROM
+			last 3FFFFF
+			bits_read {
+				0 sda
+			}
+			bits_write {
+				0 sda
+				8 scl
+			}
+		}
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/romdb.c	Sat Jul 18 10:42:15 2015 -0700
@@ -0,0 +1,780 @@
+#include <stdlib.h>
+#include <string.h>
+#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
+
+enum {
+	I2C_IDLE,
+	I2C_START,
+	I2C_DEVICE_ACK,
+	I2C_ADDRESS_HI,
+	I2C_ADDRESS_HI_ACK,
+	I2C_ADDRESS,
+	I2C_ADDRESS_ACK,
+	I2C_READ,
+	I2C_READ_ACK,
+	I2C_WRITE,
+	I2C_WRITE_ACK
+};
+
+char * i2c_states[] = {
+	"idle",
+	"start",
+	"device ack",
+	"address hi",
+	"address hi ack",
+	"address",
+	"address ack",
+	"read",
+	"read_ack",
+	"write",
+	"write_ack"
+};
+
+void eeprom_init(eeprom_state *state, uint8_t *buffer, uint32_t size)
+{
+	state->slave_sda = 1;
+	state->host_sda = state->scl = 0;
+	state->buffer = buffer;
+	state->size = size;
+	state->state = I2C_IDLE;
+}
+
+void set_host_sda(eeprom_state *state, uint8_t val)
+{
+	if (state->scl) {
+		if (val & ~state->host_sda) {
+			//low to high, stop condition
+			state->state = I2C_IDLE;
+			state->slave_sda = 1;
+		} else if (~val & state->host_sda) {
+			//high to low, start condition
+			state->state = I2C_START;
+			state->slave_sda = 1;
+			state->counter = 8;
+		}
+	}
+	state->host_sda = val;
+}
+
+void set_scl(eeprom_state *state, uint8_t val)
+{
+	if (val & ~state->scl) {
+		//low to high transition
+		switch (state->state)
+		{
+		case I2C_START:
+		case I2C_ADDRESS_HI:
+		case I2C_ADDRESS:
+		case I2C_WRITE:
+			state->latch = state->host_sda | state->latch << 1;
+			state->counter--;
+			if (!state->counter) {
+				switch (state->state & 0x7F)
+				{
+				case I2C_START:
+					state->state = I2C_DEVICE_ACK;
+					break;
+				case I2C_ADDRESS_HI:
+					state->address = state->latch << 8;
+					state->state = I2C_ADDRESS_HI_ACK;
+					break;
+				case I2C_ADDRESS:
+					state->address |= state->latch;
+					state->state = I2C_ADDRESS_ACK;
+					break;
+				case I2C_WRITE:
+					state->buffer[state->address] = state->latch;
+					state->state = I2C_WRITE_ACK;
+					state->address++;
+					//TODO: page mask
+					state->address &= state->size-1;
+					break;
+				}
+			}
+			break;
+		case I2C_DEVICE_ACK:
+			if (state->latch & 1) {
+				state->state = I2C_READ;
+				state->counter = 8;
+				state->latch = state->buffer[state->address];
+				state->address++;
+				//TODO: page mask
+				state->address &= state->size-1;
+			} else {
+				if (state->size < 256) {
+					state->address = state->latch >> 1;
+					state->state = I2C_WRITE;
+				} else if (state->size < 8192) {
+					state->address = state->latch << 8;
+					state->state = I2C_ADDRESS;
+				} else {
+					state->state = I2C_ADDRESS_HI;
+				}
+				state->counter = 8;
+			}
+			break;
+		case I2C_ADDRESS_HI_ACK:
+			state->state = I2C_ADDRESS;
+			state->counter = 8;
+			break;
+		case I2C_ADDRESS_ACK:
+			state->state = I2C_WRITE;
+			state->counter = 8;
+			break;
+		case I2C_READ:
+			state->counter--;
+			if (!state->counter) {
+				state->state = I2C_READ_ACK;
+			}
+			break;
+		case I2C_READ_ACK:
+			state->state = I2C_READ;
+			state->counter = 8;
+			break;
+		case I2C_WRITE_ACK:
+			state->state = I2C_WRITE;
+			state->counter = 8;
+			break;
+		}
+	} else if (~val & state->scl) {
+		//high to low transition
+		switch (state->state & 0x7F)
+		{
+		case I2C_DEVICE_ACK:
+		case I2C_ADDRESS_HI_ACK:
+		case I2C_ADDRESS_ACK:
+		case I2C_READ_ACK:
+		case I2C_WRITE_ACK:
+			state->slave_sda = 0;
+			break;
+		case I2C_READ:
+			state->slave_sda = state->latch >> 7;
+			state->latch = state->latch << 1;
+			break;
+		default:
+			state->slave_sda = 1;
+			break;
+		}
+	}
+	state->scl = val;
+}
+
+uint8_t get_sda(eeprom_state *state)
+{
+	return state->host_sda & state->slave_sda;
+}
+
+uint16_t read_sram_w(uint32_t address, m68k_context * context)
+{
+	genesis_context * gen = context->system;
+	address &= gen->save_ram_mask;
+	switch(gen->save_type)
+	{
+	case RAM_FLAG_BOTH:
+		return gen->save_storage[address] << 8 | gen->save_storage[address+1];
+	case RAM_FLAG_EVEN:
+		return gen->save_storage[address >> 1] << 8 | 0xFF;
+	case RAM_FLAG_ODD:
+		return gen->save_storage[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_type)
+	{
+	case RAM_FLAG_BOTH:
+		return gen->save_storage[address];
+	case RAM_FLAG_EVEN:
+		if (address & 1) {
+			return 0xFF;
+		} else {
+			return gen->save_storage[address >> 1];
+		}
+	case RAM_FLAG_ODD:
+		if (address & 1) {
+			return gen->save_storage[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_type)
+		{
+		case RAM_FLAG_BOTH:
+			gen->save_storage[address] = value >> 8;
+			gen->save_storage[address+1] = value;
+			break;
+		case RAM_FLAG_EVEN:
+			gen->save_storage[address >> 1] = value >> 8;
+			break;
+		case RAM_FLAG_ODD:
+			gen->save_storage[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_type)
+		{
+		case RAM_FLAG_BOTH:
+			gen->save_storage[address] = value;
+			break;
+		case RAM_FLAG_EVEN:
+			if (!(address & 1)) {
+				gen->save_storage[address >> 1] = value;
+			}
+			break;
+		case RAM_FLAG_ODD:
+			if (address & 1) {
+				gen->save_storage[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;
+}
+eeprom_map *find_eeprom_map(uint32_t address, genesis_context *gen)
+{
+	for (int i = 0; i < gen->num_eeprom; i++)
+	{
+		if (address >= gen->eeprom_map[i].start && address <= gen->eeprom_map[i].end) {
+			return  gen->eeprom_map + i;
+		}
+	}
+	return NULL;
+}
+
+void * write_eeprom_i2c_w(uint32_t address, void * context, uint16_t value)
+{
+	genesis_context *gen = ((m68k_context *)context)->system;
+	eeprom_map *map = find_eeprom_map(address, gen);
+	if (!map) {
+		fprintf(stderr, "Could not find EEPROM map for address %X\n", address);
+		exit(1);
+	}
+	printf("EEPROM word write: %X - %X\n", address, value);
+	if (map->scl_mask) {
+		set_scl(&gen->eeprom, (value & map->scl_mask) != 0);
+		printf("scl: %d, state: %s, counter: %d, latch: %X\n", (value & map->scl_mask) != 0, i2c_states[gen->eeprom.state], gen->eeprom.counter, gen->eeprom.latch);
+	}
+	if (map->sda_write_mask) {
+		printf("sda: %d\n", (value & map->sda_write_mask) != 0);
+		set_host_sda(&gen->eeprom, (value & map->sda_write_mask) != 0);
+	}
+	return context;
+}
+
+void * write_eeprom_i2c_b(uint32_t address, void * context, uint8_t value)
+{
+	genesis_context *gen = ((m68k_context *)context)->system;
+	eeprom_map *map = find_eeprom_map(address, gen);
+	if (!map) {
+		fprintf(stderr, "Could not find EEPROM map for address %X\n", address);
+		exit(1);
+	}
+	
+	uint16_t expanded, mask;
+	if (address & 1) {
+		expanded = value;
+		mask = 0xFF;
+	} else {
+		expanded = value << 8;
+		mask = 0xFF00;
+	}
+	printf("EEPROM byte write: %X - %X (using mask %X and expanded val %X)\n", address, value, mask, expanded);
+	if (map->scl_mask & mask) {
+		printf("scl: %d, state: %s\n", (expanded & map->scl_mask) != 0, i2c_states[gen->eeprom.state]);
+		set_scl(&gen->eeprom, (expanded & map->scl_mask) != 0);
+	}
+	if (map->sda_write_mask & mask) {
+		printf("sda: %d\n", (expanded & map->sda_write_mask) != 0);
+		set_host_sda(&gen->eeprom, (expanded & map->sda_write_mask) != 0);
+	}
+	return context;
+}
+
+uint16_t read_eeprom_i2c_w(uint32_t address, void * context)
+{
+	genesis_context *gen = ((m68k_context *)context)->system;
+	eeprom_map *map = find_eeprom_map(address, gen);
+	if (!map) {
+		fprintf(stderr, "Could not find EEPROM map for address %X\n", address);
+		exit(1);
+	}
+	uint16_t ret = 0;
+	if (map->sda_read_bit < 16) {
+		ret = get_sda(&gen->eeprom) << map->sda_read_bit;
+	}
+	printf("EEPROM word read: %X - %X\n", address, ret);
+	return ret;
+}
+
+uint8_t read_eeprom_i2c_b(uint32_t address, void * context)
+{
+	genesis_context *gen = ((m68k_context *)context)->system;
+	eeprom_map *map = find_eeprom_map(address, gen);
+	if (!map) {
+		fprintf(stderr, "Could not find EEPROM map for address %X\n", address);
+		exit(1);
+	}
+	uint8_t bit = address & 1 ? map->sda_read_bit : map->sda_read_bit - 8;
+	uint8_t ret = 0;
+	if (bit < 8) {
+		ret = get_sda(&gen->eeprom) << bit;
+	}
+	printf("EEPROM byte read: %X - %X\n", address, ret);
+	return ret;
+}
+
+tern_node *load_rom_db()
+{
+	char *exe_dir = get_exe_dir();
+	if (!exe_dir) {
+		fputs("Failed to find executable path\n", stderr);
+		exit(1);
+	}
+	char *path = alloc_concat(exe_dir, "/rom.db");
+	tern_node *db = parse_config_file(path);
+	free(path);
+	if (!db) {
+		fputs("Failed to load ROM DB\n", stderr);
+	}
+	return db;
+}
+
+char *get_header_name(uint8_t *rom)
+{
+	uint8_t *last = rom + TITLE_END - 1;
+	uint8_t *src = rom + TITLE_START;
+	
+	while (last > src && (*last <=  0x20 || *last >= 0x80))
+	{
+		last--;
+	}
+	if (last == src) {
+		//TODO: Use other name field
+		return strdup("UNKNOWN");
+	} else {
+		last++;
+		char *ret = malloc(last - (rom + TITLE_START) + 1);
+		uint8_t *dst;
+		for (dst = ret; src < last; src++)
+		{
+			if (*src >= 0x20 && *src < 0x80) {
+				*(dst++) = *src;
+			}
+		}
+		*dst = 0;
+		return ret;
+	}
+}
+
+char *region_chars = "UB4JEA";
+uint8_t region_bits[] = {REGION_U, REGION_U, REGION_U, REGION_J, REGION_E, REGION_E};
+
+uint8_t translate_region_char(uint8_t c)
+{
+	for (int i = 0; i < sizeof(region_bits); i++)
+	{
+		if (c == region_chars[i]) {
+			return region_bits[i];
+		}
+	}
+	return 0;
+}
+
+uint8_t get_header_regions(uint8_t *rom)
+{
+	uint8_t regions = 0;
+	for (int i = 0; i < 3; i++)
+	{
+		regions |= translate_region_char(rom[REGION_START + i]);
+	}
+	return regions;
+}
+
+uint32_t get_u32be(uint8_t *data)
+{
+	return *data << 24 | data[1] << 16 | data[2] << 8 | data[3];
+}
+
+uint32_t calc_mask(uint32_t src_size, uint32_t start, uint32_t end)
+{
+	uint32_t map_size = end-start+1;
+	if (src_size < map_size) {
+		return nearest_pow2(src_size)-1;
+	} else if (!start) {
+		return 0xFFFFFF;
+	} else {
+		return nearest_pow2(map_size)-1;
+	}
+}
+
+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->save_mask = ram_end - ram_start;
+		uint32_t size = info->save_mask + 1;
+		if (ram_flags != RAM_FLAG_BOTH) {
+			size /= 2;
+		}
+		info->save_size = size;
+		info->save_buffer = malloc(size);
+		
+		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 = info->save_mask;
+			info->map[1].end = ram_end + 1;
+			info->map[1].flags = MMAP_READ | MMAP_WRITE;
+			
+			if (ram_flags == RAM_FLAG_ODD) {
+				info->map[1].flags |= MMAP_ONLY_ODD;
+			} else if (ram_flags == RAM_FLAG_EVEN) {
+				info->map[1].flags |= MMAP_ONLY_EVEN;
+			}
+			info->map[1].buffer = info->save_buffer;
+		} 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;
+}
+
+typedef struct {
+	rom_info     *info;
+	uint8_t      *rom;
+	tern_node    *root;
+	uint32_t     rom_size;
+	int          index;
+	int          num_els;
+} map_iter_state;
+
+void eeprom_read_fun(char *key, tern_val val, void *data)
+{
+	int bit = atoi(key);
+	if (bit < 0 || bit > 15) {
+		fprintf(stderr, "bit %s is out of range", key);
+		return;
+	}
+	char *pin = val.ptrval;
+	if (strcmp(pin, "sda")) {
+		fprintf(stderr, "bit %s is connected to unrecognized read pin %s", key, pin);
+		return;
+	}
+	eeprom_map *map = data;
+	map->sda_read_bit = bit;
+}
+
+void eeprom_write_fun(char *key, tern_val val, void *data)
+{
+	int bit = atoi(key);
+	if (bit < 0 || bit > 15) {
+		fprintf(stderr, "bit %s is out of range", key);
+		return;
+	}
+	char *pin = val.ptrval;
+	eeprom_map *map = data;
+	if (!strcmp(pin, "sda")) {
+		map->sda_write_mask = 1 << bit;
+		return;
+	}
+	if (!strcmp(pin, "scl")) {
+		map->scl_mask = 1 << bit;
+		return;
+	}
+	fprintf(stderr, "bit %s is connected to unrecognized write pin %s", key, pin);
+}
+
+void map_iter_fun(char *key, tern_val val, void *data)
+{
+	map_iter_state *state = data;
+	tern_node *node = tern_get_node(val);
+	if (!node) {
+		fprintf(stderr, "ROM DB map entry %d with address %s is not a node\n", state->index, key);
+		exit(1);
+	}
+	uint32_t start = strtol(key, NULL, 16);
+	uint32_t end = strtol(tern_find_ptr_default(node, "last", "0"), NULL, 16);
+	if (!end || end < start) {
+		fprintf(stderr, "'last' value is missing or invalid for ROM DB map entry %d with address %s\n", state->index, key);
+		exit(1);
+	}
+	char * dtype = tern_find_ptr_default(node, "device", "ROM");
+	uint32_t offset = strtol(tern_find_ptr_default(node, "offset", "0"), NULL, 0);
+	memmap_chunk *map = state->info->map + state->index;
+	map->start = start;
+	map->end = end;
+	if (!strcmp(dtype, "ROM")) {
+		map->buffer = state->rom + offset;
+		map->flags = MMAP_READ;
+		map->mask = calc_mask(state->rom_size, start, end);
+	} else if (!strcmp(dtype, "EEPROM")) {
+		if (!state->info->save_size) {
+			char * size = tern_find_path(state->root, "EEPROM\0size\0").ptrval;
+			if (!size) {
+				fprintf(stderr, "ROM DB map entry %d with address %s has device type EEPROM, but the EEPROM size is not defined\n", state->index, key);
+				exit(1);
+			}
+			state->info->save_size = atoi(size);
+			if (!state->info->save_size) {
+				fprintf(stderr, "EEPROM size %s is invalid\n", size);
+				exit(1);
+			}
+			char *etype = tern_find_path(state->root, "EEPROM\0type\0").ptrval;
+			if (!etype) {
+				etype = "i2c";
+			}
+			if (!strcmp(etype, "i2c")) {
+				state->info->save_type = SAVE_I2C;
+			} else {
+				fprintf(stderr, "EEPROM type %s is invalid\n", etype);
+				exit(1);
+			}
+			state->info->save_buffer = malloc(state->info->save_size);
+			state->info->eeprom_map = malloc(sizeof(eeprom_map) * state->num_els);
+			memset(state->info->eeprom_map, 0, sizeof(eeprom_map) * state->num_els);
+		}
+		eeprom_map *eep_map = state->info->eeprom_map + state->info->num_eeprom;
+		eep_map->start = start;
+		eep_map->end = end;
+		eep_map->sda_read_bit = 0xFF;
+		tern_node * bits_read = tern_find_ptr(node, "bits_read");
+		if (bits_read) {
+			tern_foreach(bits_read, eeprom_read_fun, eep_map);
+		}
+		tern_node * bits_write = tern_find_ptr(node, "bits_write");
+		if (bits_write) {
+			tern_foreach(bits_write, eeprom_write_fun, eep_map);
+		}
+		printf("EEPROM address %X: sda read: %X, sda write: %X, scl: %X\n", start, eep_map->sda_read_bit, eep_map->sda_write_mask, eep_map->scl_mask);
+		state->info->num_eeprom++;
+		map->write_16 = write_eeprom_i2c_w;
+		map->write_8 = write_eeprom_i2c_b;
+		map->read_16 = read_eeprom_i2c_w;
+		map->read_8 = read_eeprom_i2c_b;
+		map->mask = 0xFFFFFF;
+	} else if (!strcmp(dtype, "SRAM")) {
+		if (!state->info->save_size) {
+			char * size = tern_find_path(state->root, "SRAM\0size\0").ptrval;
+			if (!size) {
+				fprintf(stderr, "ROM DB map entry %d with address %s has device type SRAM, but the SRAM size is not defined\n", state->index, key);
+				exit(1);
+			}
+			state->info->save_size = atoi(size);
+			if (!state->info->save_size) {
+				fprintf(stderr, "SRAM size %s is invalid\n", size);
+				exit(1);
+			}
+			state->info->save_buffer = malloc(state->info->save_size);
+			char *bus = tern_find_path(state->root, "SRAM\0bus\0").ptrval;
+			if (!strcmp(bus, "odd")) {
+				state->info->save_type = RAM_FLAG_ODD;
+			} else if(!strcmp(bus, "even")) {
+				state->info->save_type = RAM_FLAG_EVEN;
+			} else {
+				state->info->save_type = RAM_FLAG_BOTH;
+			}
+		}
+		map->buffer = state->info->save_buffer + offset;
+		map->flags = MMAP_READ | MMAP_WRITE;
+		if (state->info->save_type == RAM_FLAG_ODD) {
+			map->flags |= MMAP_ONLY_ODD;
+		} else if(state->info->save_type == RAM_FLAG_EVEN) {
+			map->flags |= MMAP_ONLY_EVEN;
+		}
+		map->mask = calc_mask(state->info->save_size, start, end);
+	} else {
+		fprintf(stderr, "Invalid device type for ROM DB map entry %d with address %s\n", state->index, key);
+		exit(1);
+	}
+	state->index++;
+}
+
+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;
+	product_id[GAME_ID_LEN] = 0;
+	for (int i = 0; i < GAME_ID_LEN; i++)
+	{
+		if (rom[GAME_ID_OFF + i] <= ' ') {
+			product_id[i] = 0;
+			break;
+		}
+		product_id[i] = rom[GAME_ID_OFF + i];
+		
+	}
+	printf("Product ID: %s\n", product_id);
+	tern_node * entry = tern_find_ptr(rom_db, product_id);
+	if (!entry) {
+		puts("Not found in ROM DB, examining header\n");
+		return configure_rom_heuristics(rom, rom_size, base_map, base_chunks);
+	}
+	rom_info info;
+	info.name = tern_find_ptr(entry, "name");
+	if (info.name) {
+		printf("Found name: %s\n", info.name);
+		info.name = strdup(info.name);
+	} else {
+		info.name = get_header_name(rom);
+	}
+	
+	char *dbreg = tern_find_ptr(entry, "regions");
+	info.regions = 0;
+	if (dbreg) {
+		while (*dbreg != 0)
+		{
+			info.regions |= translate_region_char(*(dbreg++));
+		}
+	}
+	if (!info.regions) {
+		info.regions = get_header_regions(rom);
+	}
+	
+	tern_node *map = tern_find_ptr(entry, "map");
+	if (map) {
+		info.map_chunks = tern_count(map);
+		if (info.map_chunks) {
+			info.map_chunks += base_chunks;
+			info.save_buffer = NULL;
+			info.save_size = 0;
+			info.map = malloc(sizeof(memmap_chunk) * info.map_chunks);
+			info.eeprom_map = NULL;
+			info.num_eeprom = 0;
+			memset(info.map, 0, sizeof(memmap_chunk) * (info.map_chunks - base_chunks));
+			map_iter_state state = {&info, rom, entry, rom_size, 0, info.map_chunks - base_chunks};
+			tern_foreach(map, map_iter_fun, &state);
+			memcpy(info.map + state.index, base_map, sizeof(memmap_chunk) * base_chunks);
+		} 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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/romdb.h	Sat Jul 18 10:42:15 2015 -0700
@@ -0,0 +1,57 @@
+#ifndef ROMDB_H_
+#define ROMDB_H_
+
+#define REGION_J 1
+#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 {
+	uint32_t     start;
+	uint32_t     end;
+	uint16_t     sda_write_mask;
+	uint16_t     scl_mask;
+	uint8_t      sda_read_bit;
+} eeprom_map;
+
+typedef struct {
+	char        *buffer;
+	uint32_t    size;
+	uint16_t    address;
+	uint8_t     host_sda;
+	uint8_t     slave_sda;
+	uint8_t     scl;
+	uint8_t     state;
+	uint8_t     counter;
+	uint8_t     latch;
+} eeprom_state;
+
+typedef struct {
+	char          *name;
+	memmap_chunk  *map;
+	uint8_t       *save_buffer;
+	eeprom_map    *eeprom_map;
+	uint32_t      num_eeprom;
+	uint32_t      map_chunks;
+	uint32_t      save_size;
+	uint32_t      save_mask;
+	uint8_t       save_type;
+	uint8_t       regions;
+} rom_info;
+
+tern_node *load_rom_db();
+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);
+void eeprom_init(eeprom_state *state, uint8_t *buffer, uint32_t size);
+
+#endif //ROMDB_H_
--- a/tern.c	Sun Jun 28 19:23:38 2015 -0700
+++ b/tern.c	Sat Jul 18 10:42:15 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)
 {
@@ -105,7 +107,11 @@
 {
 	tern_val ret;
 	if (tern_find(head, key, &ret)) {
-		return ret.ptrval;
+		if (ret.intval & 1) {
+			return (void *)(ret.intval & ~1);
+		} else {
+			return ret.ptrval;
+		}
 	}
 	return def;
 }
@@ -115,6 +121,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 +154,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->right, 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 +219,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	Sun Jun 28 19:23:38 2015 -0700
+++ b/tern.h	Sat Jul 18 10:42:15 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,13 @@
 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);
+void tern_foreach(tern_node *head, iter_fun fun, void *data);
 char * tern_int_key(uint32_t key, char * buf);
+tern_node * tern_get_node(tern_val value);
 
 #endif //TERN_H_
--- a/util.c	Sun Jun 28 19:23:38 2015 -0700
+++ b/util.c	Sat Jul 18 10:42:15 2015 -0700
@@ -2,6 +2,7 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <ctype.h>
+#include <stdint.h>
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -68,6 +69,16 @@
 	return text+1;
 }
 
+uint32_t nearest_pow2(uint32_t val)
+{
+	uint32_t ret = 1;
+	while (ret < val)
+	{
+		ret = ret << 1;
+	}
+	return ret;
+}
+
 static char * exe_str;
 
 void set_exe_str(char * str)
--- a/util.h	Sun Jun 28 19:23:38 2015 -0700
+++ b/util.h	Sat Jul 18 10:42:15 2015 -0700
@@ -15,6 +15,8 @@
 char * strip_ws(char * text);
 //Inserts a null after the first word, returns a pointer to the second word
 char * split_keyval(char * text);
+//Gets the smallest power of two that is >= a certain value, won't work for values > 0x80000000
+uint32_t nearest_pow2(uint32_t val);
 //Should be called by main with the value of argv[0] for use by get_exe_dir
 void set_exe_str(char * str);
 //Returns the directory the executable is in
--- a/vdp.c	Sun Jun 28 19:23:38 2015 -0700
+++ b/vdp.c	Sat Jul 18 10:42:15 2015 -0700
@@ -910,15 +910,16 @@
 				}
 			}
 		} else {
-			uint32_t cell = (line / 8) * (context->regs[REG_MODE_4] & BIT_H40 ? 40 : 32) + col;
-			uint32_t address = cell * 32 + (line % 8) * 4;
+			uint32_t base = (context->debug - 3) * 0x200;
+			uint32_t cell = base + (line / 8) * (context->regs[REG_MODE_4] & BIT_H40 ? 40 : 32) + col;
+			uint32_t address = (cell * 32 + (line % 8) * 4) & 0xFFFF;
 			for (int32_t i = 0; i < 4; i ++) {
 				*(dst++) = context->colors[(context->debug_pal << 4) | (context->vdpmem[address] >> 4)];
 				*(dst++) = context->colors[(context->debug_pal << 4) | (context->vdpmem[address] & 0xF)];
 				address++;
 			}
 			cell++;
-			address = cell * 32 + (line % 8) * 4;
+			address = (cell * 32 + (line % 8) * 4) & 0xFFFF;
 			for (int32_t i = 0; i < 4; i ++) {
 				*(dst++) = context->colors[(context->debug_pal << 4) | (context->vdpmem[address] >> 4)];
 				*(dst++) = context->colors[(context->debug_pal << 4) | (context->vdpmem[address] & 0xF)];
@@ -1739,7 +1740,7 @@
 	cur->cycle = context->cycles + ((context->regs[REG_MODE_4] & BIT_H40) ? 16 : 20)*FIFO_LATENCY;
 	cur->address = context->address;
 	cur->value = value;
-	if (context->cd & 0x20 && (context->regs[REG_DMASRC_H] & 0xC0) == 0x80) {
+	if (context->cd & 0x20 && (context->regs[REG_DMASRC_H] & 0xC0) == 0x80 && (context->regs[REG_MODE_2] & BIT_DMA_ENABLE)) {
 		context->flags |= FLAG_DMA_RUN;
 	}
 	cur->cd = context->cd;