changeset 1433:c886c54d8cf1

Added save states to SMS emulation
author Michael Pavone <pavone@retrodev.com>
date Wed, 16 Aug 2017 20:06:28 -0700
parents 5e7e6d9b79ff
children b9dbc823c014
files genesis.c menu.c serialize.h sms.c system.h z80_to_x86.c
diffstat 6 files changed, 222 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- a/genesis.c	Sun Aug 13 22:59:01 2017 -0700
+++ b/genesis.c	Wed Aug 16 20:06:28 2017 -0700
@@ -1003,6 +1003,41 @@
 	vdp_release_framebuffer(gen->vdp);
 }
 
+#include "m68k_internal.h" //needed for get_native_address_trans, should be eliminated once handling of PC is cleaned up
+static uint8_t load_state(system_header *system, uint8_t slot)
+{
+	genesis_context *gen = (genesis_context *)system;
+	char numslotname[] = "slot_0.state";
+	char *slotname;
+	if (slot == QUICK_SAVE_SLOT) {
+		slotname = "quicksave.state";
+	} else {
+		numslotname[5] = '0' + slot;
+		slotname = numslotname;
+	}
+	char const *parts[] = {gen->header.next_context->save_dir, PATH_SEP, slotname};
+	char *statepath = alloc_concat_m(3, parts);
+	deserialize_buffer state;
+	uint32_t pc = 0;
+	uint8_t ret;
+	if (load_from_file(&state, statepath)) {
+		genesis_deserialize(&state, gen);
+		free(state.data);
+		//HACK
+		pc = gen->m68k->last_prefetch_address;
+		ret = 1;
+	} else {
+		strcpy(statepath + strlen(statepath)-strlen("state"), "gst");
+		pc = load_gst(gen, statepath);
+		ret = pc != 0;
+	}
+	if (ret) {
+		gen->m68k->resume_pc = get_native_address_trans(gen->m68k, pc);
+	}
+	free(statepath);
+	return ret;
+}
+
 static void start_genesis(system_header *system, char *statefile)
 {
 	genesis_context *gen = (genesis_context *)system;
@@ -1146,6 +1181,7 @@
 	gen->header.resume_context = resume_genesis;
 	gen->header.load_save = load_save;
 	gen->header.persist_save = persist_save;
+	gen->header.load_state = load_state;
 	gen->header.soft_reset = soft_reset;
 	gen->header.free_context = free_genesis;
 	gen->header.get_open_bus_value = get_open_bus_value;
--- a/menu.c	Sun Aug 13 22:59:01 2017 -0700
+++ b/menu.c	Wed Aug 16 20:06:28 2017 -0700
@@ -420,6 +420,9 @@
 		case 6:
 			//load state
 			if (gen->header.next_context && gen->header.next_context->save_dir) {
+				if (!gen->header.next_context->load_state(gen->header.next_context, dst)) {
+					break;
+				}/*
 				char numslotname[] = "slot_0.state";
 				char *slotname;
 				if (dst == QUICK_SAVE_SLOT) {
@@ -430,6 +433,7 @@
 				}
 				char const *parts[] = {gen->header.next_context->save_dir, PATH_SEP, slotname};
 				char *statepath = alloc_concat_m(3, parts);
+				gen->header.next_context->load_state
 				genesis_context *next = (genesis_context *)gen->header.next_context;
 				deserialize_buffer state;
 				uint32_t pc = 0;
@@ -447,6 +451,7 @@
 					break;
 				}
 				next->m68k->resume_pc = get_native_address_trans(next->m68k, pc);
+				*/
 			}
 			m68k->should_return = 1;
 			break;
--- a/serialize.h	Sun Aug 13 22:59:01 2017 -0700
+++ b/serialize.h	Wed Aug 16 20:06:28 2017 -0700
@@ -40,9 +40,9 @@
 	SECTION_SEGA_IO_EXT,
 	SECTION_MAIN_RAM,
 	SECTION_SOUND_RAM,
-	SECTION_SEGA_MAPPER,
-	SECTION_SMS_MAPPER,
-	SECTION_EEPROM
+	SECTION_MAPPER,
+	SECTION_EEPROM,
+	SECTION_CART_RAM
 };
 
 void init_serialize(serialize_buffer *buf);
--- a/sms.c	Sun Aug 13 22:59:01 2017 -0700
+++ b/sms.c	Wed Aug 16 20:06:28 2017 -0700
@@ -109,14 +109,10 @@
 	return 0xFF;
 }
 
-static void *mapper_write(uint32_t location, void *vcontext, uint8_t value)
+static void update_mem_map(uint32_t location, sms_context *sms, uint8_t value)
 {
-	z80_context *z80 = vcontext;
-	sms_context *sms = z80->system;
+	z80_context *z80 = sms->z80;
 	void *old_value;
-	location &= 3;
-	sms->ram[0x1FFC + location] = value;
-	sms->bank_regs[location] = value;
 	if (location) {
 		uint32_t idx = location - 1;
 		old_value = z80->mem_pointers[idx];
@@ -139,6 +135,16 @@
 			z80_invalidate_code_range(z80, 0x8000, 0xC000);
 		}
 	}
+}
+
+static void *mapper_write(uint32_t location, void *vcontext, uint8_t value)
+{
+	z80_context *z80 = vcontext;
+	sms_context *sms = z80->system;
+	location &= 3;
+	sms->ram[0x1FFC + location] = value;
+	sms->bank_regs[location] = value;
+	update_mem_map(location, sms, value);
 	return vcontext;
 }
 
@@ -189,6 +195,150 @@
 	psg_adjust_master_clock(context->psg, context->master_clock);
 }
 
+void sms_serialize(sms_context *sms, serialize_buffer *buf)
+{
+	start_section(buf, SECTION_Z80);
+	z80_serialize(sms->z80, buf);
+	end_section(buf);
+	
+	start_section(buf, SECTION_VDP);
+	vdp_serialize(sms->vdp, buf);
+	end_section(buf);
+	
+	start_section(buf, SECTION_PSG);
+	psg_serialize(sms->psg, buf);
+	end_section(buf);
+	
+	start_section(buf, SECTION_SEGA_IO_1);
+	io_serialize(sms->io.ports, buf);
+	end_section(buf);
+	
+	start_section(buf, SECTION_SEGA_IO_2);
+	io_serialize(sms->io.ports + 1, buf);
+	end_section(buf);
+	
+	start_section(buf, SECTION_MAIN_RAM);
+	save_int8(buf, sizeof(sms->ram) / 1024);
+	save_buffer8(buf, sms->ram, sizeof(sms->ram));
+	end_section(buf);
+	
+	start_section(buf, SECTION_MAPPER);
+	save_int8(buf, 1);//mapper type, 1 for Sega mapper
+	save_buffer8(buf, sms->bank_regs, sizeof(sms->bank_regs));
+	end_section(buf);
+	
+	start_section(buf, SECTION_CART_RAM);
+	save_int8(buf, SMS_CART_RAM_SIZE / 1024);
+	save_buffer8(buf, sms->cart_ram, SMS_CART_RAM_SIZE);
+	end_section(buf);
+}
+
+static void ram_deserialize(deserialize_buffer *buf, void *vsms)
+{
+	sms_context *sms = vsms;
+	uint32_t ram_size = load_int8(buf) * 1024;
+	if (ram_size > sizeof(sms->ram)) {
+		fatal_error("State has a RAM size of %d bytes", ram_size);
+	}
+	load_buffer8(buf, sms->ram, ram_size);
+}
+
+static void cart_ram_deserialize(deserialize_buffer *buf, void *vsms)
+{
+	sms_context *sms = vsms;
+	uint32_t ram_size = load_int8(buf) * 1024;
+	if (ram_size > SMS_CART_RAM_SIZE) {
+		fatal_error("State has a cart RAM size of %d bytes", ram_size);
+	}
+	load_buffer8(buf, sms->cart_ram, ram_size);
+}
+
+static void mapper_deserialize(deserialize_buffer *buf, void *vsms)
+{
+	sms_context *sms = vsms;
+	uint8_t mapper_type = load_int8(buf);
+	if (mapper_type != 1) {
+		warning("State contains an unrecognized mapper type %d, it may be from a newer version of BlastEm\n", mapper_type);
+		return;
+	}
+	for (int i = 0; i < sizeof(sms->bank_regs); i++)
+	{
+		sms->bank_regs[i] = load_int8(buf);
+		update_mem_map(i, sms, sms->bank_regs[i]);
+	}
+}
+
+void sms_deserialize(deserialize_buffer *buf, sms_context *sms)
+{
+	register_section_handler(buf, (section_handler){.fun = z80_deserialize, .data = sms->z80}, SECTION_Z80);
+	register_section_handler(buf, (section_handler){.fun = vdp_deserialize, .data = sms->vdp}, SECTION_VDP);
+	register_section_handler(buf, (section_handler){.fun = psg_deserialize, .data = sms->psg}, SECTION_PSG);
+	register_section_handler(buf, (section_handler){.fun = io_deserialize, .data = sms->io.ports}, SECTION_SEGA_IO_1);
+	register_section_handler(buf, (section_handler){.fun = io_deserialize, .data = sms->io.ports + 1}, SECTION_SEGA_IO_2);
+	register_section_handler(buf, (section_handler){.fun = ram_deserialize, .data = sms}, SECTION_MAIN_RAM);
+	register_section_handler(buf, (section_handler){.fun = mapper_deserialize, .data = sms}, SECTION_MAPPER);
+	register_section_handler(buf, (section_handler){.fun = cart_ram_deserialize, .data = sms}, SECTION_CART_RAM);
+	//TODO: cart RAM
+	while (buf->cur_pos < buf->size)
+	{
+		load_section(buf);
+	}
+	z80_invalidate_code_range(sms->z80, 0xC000, 0x10000);
+	if (sms->bank_regs[0] & 8) {
+		//cart RAM is enabled, invalidate the region in case there is any code there
+		z80_invalidate_code_range(sms->z80, 0x8000, 0xC000);
+	}
+}
+
+static void save_state(sms_context *sms, uint8_t slot)
+{
+	char *save_path;
+	if (slot == QUICK_SAVE_SLOT) {
+		save_path = save_state_path;
+	} else {
+		char slotname[] = "slot_0.state";
+		slotname[5] = '0' + slot;
+		char const *parts[] = {sms->header.save_dir, PATH_SEP, slotname};
+		save_path = alloc_concat_m(3, parts);
+	}
+	serialize_buffer state;
+	init_serialize(&state);
+	sms_serialize(sms, &state);
+	save_to_file(&state, save_path);
+	printf("Saved state to %s\n", save_path);
+	free(state.data);
+}
+
+static uint8_t load_state_path(sms_context *sms, char *path)
+{
+	deserialize_buffer state;
+	uint8_t ret;
+	if ((ret = load_from_file(&state, path))) {
+		sms_deserialize(&state, sms);
+		free(state.data);
+		printf("Loaded %s\n", path);
+	}
+	return ret;
+}
+
+static uint8_t load_state(system_header *system, uint8_t slot)
+{
+	sms_context *sms = (sms_context *)system;
+	char numslotname[] = "slot_0.state";
+	char *slotname;
+	if (slot == QUICK_SAVE_SLOT) {
+		slotname = "quicksave.state";
+	} else {
+		numslotname[5] = '0' + slot;
+		slotname = numslotname;
+	}
+	char const *parts[] = {sms->header.save_dir, PATH_SEP, slotname};
+	char *statepath = alloc_concat_m(3, parts);
+	uint8_t ret = load_state_path(sms, statepath);
+	free(statepath);
+	return ret;
+}
+
 static void run_sms(system_header *system)
 {
 	render_disable_ym();
@@ -215,6 +365,16 @@
 		target_cycle = sms->z80->current_cycle;
 		vdp_run_context(sms->vdp, target_cycle);
 		psg_run(sms->psg, target_cycle);
+		
+		if (system->save_state) {
+			while (!sms->z80->pc) {
+				//advance Z80 to an instruction boundary
+				z80_run(sms->z80, sms->z80->current_cycle + 1);
+			}
+			save_state(sms, system->save_state - 1);
+			system->save_state = 0;
+		}
+		
 		target_cycle += 3420*16;
 		if (target_cycle > 0x10000000) {
 			uint32_t adjust = sms->z80->current_cycle - 3420*262*2;
@@ -243,14 +403,18 @@
 	sms_context *sms = (sms_context *)system;
 	set_keybindings(&sms->io);
 	
+	z80_assert_reset(sms->z80, 0);
+	z80_clear_reset(sms->z80, 128*15);
+	
+	if (statefile) {
+		load_state_path(sms, statefile);
+	}
+	
 	if (system->enter_debugger) {
 		system->enter_debugger = 0;
-		zinsert_breakpoint(sms->z80, 0, (uint8_t *)zdebugger);
+		zinsert_breakpoint(sms->z80, sms->z80->pc, (uint8_t *)zdebugger);
 	}
 	
-	z80_assert_reset(sms->z80, 0);
-	z80_clear_reset(sms->z80, 128*15);
-	
 	run_sms(system);
 }
 
@@ -372,6 +536,7 @@
 	sms->header.resume_context = resume_sms;
 	sms->header.load_save = load_save;
 	sms->header.persist_save = persist_save;
+	sms->header.load_state = load_state;
 	sms->header.free_context = free_sms;
 	sms->header.get_open_bus_value = get_open_bus_value;
 	sms->header.request_exit = request_exit;
--- a/system.h	Sun Aug 13 22:59:01 2017 -0700
+++ b/system.h	Wed Aug 16 20:06:28 2017 -0700
@@ -22,6 +22,7 @@
 typedef void (*system_str_fun)(system_header *, char *);
 typedef uint8_t (*system_str_fun_r8)(system_header *, char *);
 typedef void (*speed_system_fun)(system_header *, uint32_t);
+typedef uint8_t (*system_u8_fun_r8)(system_header *, uint8_t);
 
 #include "arena.h"
 #include "romdb.h"
@@ -32,6 +33,7 @@
 	system_fun        resume_context;
 	system_fun        load_save;
 	system_fun        persist_save;
+	system_u8_fun_r8  load_state;
 	system_fun        request_exit;
 	system_fun        soft_reset;
 	system_fun        free_context;
--- a/z80_to_x86.c	Sun Aug 13 22:59:01 2017 -0700
+++ b/z80_to_x86.c	Wed Aug 16 20:06:28 2017 -0700
@@ -3929,5 +3929,6 @@
 	context->int_pulse_start = load_int32(buf);
 	context->int_pulse_end = load_int32(buf);
 	context->nmi_start = load_int32(buf);
+	context->native_pc = NULL;
 }