diff genesis.c @ 2280:9ead0fe69d9b

Implement savestate support for Sega CD
author Michael Pavone <pavone@retrodev.com>
date Sun, 08 Jan 2023 14:42:24 -0800
parents 5a21bc0ec583
children b9fed07f19e4
line wrap: on
line diff
--- a/genesis.c	Sun Jan 08 14:20:43 2023 -0800
+++ b/genesis.c	Sun Jan 08 14:42:24 2023 -0800
@@ -81,6 +81,9 @@
 		save_int8(buf, gen->z80->reset);
 		save_int8(buf, gen->z80->busreq);
 		save_int16(buf, gen->z80_bank_reg);
+		//I think the refresh logic may technically be part of the VDP, but whatever
+		save_int32(buf, gen->last_sync_cycle);
+		save_int32(buf, gen->refresh_counter);
 		end_section(buf);
 
 		start_section(buf, SECTION_SEGA_IO_1);
@@ -117,6 +120,10 @@
 
 		cart_serialize(&gen->header, buf);
 	}
+	if (gen->expansion) {
+		segacd_context *cd = gen->expansion;
+		segacd_serialize(cd, buf, all);
+	}
 }
 
 static uint8_t *serialize(system_header *sys, size_t *size_out)
@@ -182,6 +189,15 @@
 	gen->z80->reset = load_int8(buf);
 	gen->z80->busreq = load_int8(buf);
 	gen->z80_bank_reg = load_int16(buf) & 0x1FF;
+	if ((buf->size - buf->cur_pos) >= 2 * sizeof(uint32_t)) {
+		gen->last_sync_cycle = load_int32(buf);
+		gen->refresh_counter = load_int32(buf);
+	} else {
+		//save state is from an older version of BlastEm that lacks these fields
+		//set them to reasonable values
+		gen->last_sync_cycle = gen->m68k->current_cycle;
+		gen->refresh_counter = 0;
+	}
 }
 
 static void tmss_deserialize(deserialize_buffer *buf, void *vgen)
@@ -194,6 +210,7 @@
 static void adjust_int_cycle(m68k_context * context, vdp_context * v_context);
 static void check_tmss_lock(genesis_context *gen);
 static void toggle_tmss_rom(genesis_context *gen);
+#include "m68k_internal.h" //needed for get_native_address_trans, should be eliminated once handling of PC is cleaned up
 void genesis_deserialize(deserialize_buffer *buf, genesis_context *gen)
 {
 	register_section_handler(buf, (section_handler){.fun = m68k_deserialize, .data = gen->m68k}, SECTION_68000);
@@ -209,6 +226,10 @@
 	register_section_handler(buf, (section_handler){.fun = zram_deserialize, .data = gen}, SECTION_SOUND_RAM);
 	register_section_handler(buf, (section_handler){.fun = cart_deserialize, .data = gen}, SECTION_MAPPER);
 	register_section_handler(buf, (section_handler){.fun = tmss_deserialize, .data = gen}, SECTION_TMSS);
+	if (gen->expansion) {
+		segacd_context *cd = gen->expansion;
+		segacd_register_section_handlers(cd, buf);
+	}
 	uint8_t tmss_old = gen->tmss;
 	gen->tmss = 0xFF;
 	while (buf->cur_pos < buf->size)
@@ -230,19 +251,24 @@
 	}
 	update_z80_bank_pointer(gen);
 	adjust_int_cycle(gen->m68k, gen->vdp);
+	//HACK: Fix this once PC/IR is represented in a better way in 68K core
+	//Would be better for this hack to live in side the 68K core itself, but it's better to do it
+	//after RAM has been loaded to avoid any unnecessary retranslation
+	gen->m68k->resume_pc = get_native_address_trans(gen->m68k, gen->m68k->last_prefetch_address);
+	if (gen->expansion) {
+		segacd_context *cd = gen->expansion;
+		cd->m68k->resume_pc = get_native_address_trans(cd->m68k, cd->m68k->last_prefetch_address);
+	}
 	free(buf->handlers);
 	buf->handlers = NULL;
 }
 
-#include "m68k_internal.h" //needed for get_native_address_trans, should be eliminated once handling of PC is cleaned up
 static void deserialize(system_header *sys, uint8_t *data, size_t size)
 {
 	genesis_context *gen = (genesis_context *)sys;
 	deserialize_buffer buffer;
 	init_deserialize(&buffer, data, size);
 	genesis_deserialize(&buffer, gen);
-	//HACK: Fix this once PC/IR is represented in a better way in 68K core
-	gen->m68k->resume_pc = get_native_address_trans(gen->m68k, gen->m68k->last_prefetch_address);
 }
 
 uint16_t read_dma_value(uint32_t address)
@@ -424,14 +450,8 @@
 	//printf("Target: %d, YM bufferpos: %d, PSG bufferpos: %d\n", target, gen->ym->buffer_pos, gen->psg->buffer_pos * 2);
 }
 
-//My refresh emulation isn't currently good enough and causes more problems than it solves
-#define REFRESH_EMULATION
-#ifdef REFRESH_EMULATION
 #define REFRESH_INTERVAL 128
 #define REFRESH_DELAY 2
-uint32_t last_sync_cycle;
-uint32_t refresh_counter;
-#endif
 
 #include <limits.h>
 #define ADJUST_BUFFER (8*MCLKS_LINE*313)
@@ -442,14 +462,12 @@
 	genesis_context * gen = context->system;
 	vdp_context * v_context = gen->vdp;
 	z80_context * z_context = gen->z80;
-#ifdef REFRESH_EMULATION
 	//lame estimation of refresh cycle delay
-	refresh_counter += context->current_cycle - last_sync_cycle;
+	gen->refresh_counter += context->current_cycle - gen->last_sync_cycle;
 	if (!gen->bus_busy) {
-		context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL));
+		context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (gen->refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL));
 	}
-	refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
-#endif
+	gen->refresh_counter = gen->refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
 
 	uint32_t mclks = context->current_cycle;
 	sync_z80(gen, mclks);
@@ -587,9 +605,7 @@
 			context->sync_cycle = context->current_cycle + 1;
 		}
 	}
-#ifdef REFRESH_EMULATION
-	last_sync_cycle = context->current_cycle;
-#endif
+	gen->last_sync_cycle = context->current_cycle;
 	return context;
 }
 
@@ -604,13 +620,13 @@
 	}
 	vdp_port &= 0x1F;
 	//printf("vdp_port write: %X, value: %X, cycle: %d\n", vdp_port, value, context->current_cycle);
-#ifdef REFRESH_EMULATION
+
 	//do refresh check here so we can avoid adding a penalty for a refresh that happens during a VDP access
-	refresh_counter += context->current_cycle - 4*MCLKS_PER_68K - last_sync_cycle;
-	context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL));
-	refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
-	last_sync_cycle = context->current_cycle;
-#endif
+	gen->refresh_counter += context->current_cycle - 4*MCLKS_PER_68K - gen->last_sync_cycle;
+	context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (gen->refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL));
+	gen->refresh_counter = gen->refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
+	gen->last_sync_cycle = context->current_cycle;
+
 	sync_components(context, 0);
 	vdp_context *v_context = gen->vdp;
 	uint32_t before_cycle = v_context->cycles;
@@ -687,18 +703,17 @@
 	} else {
 		vdp_test_port_write(gen->vdp, value);
 	}
-#ifdef REFRESH_EMULATION
-	last_sync_cycle -= 4 * MCLKS_PER_68K;
+
+	gen->last_sync_cycle -= 4 * MCLKS_PER_68K;
 	//refresh may have happened while we were waiting on the VDP,
 	//so advance refresh_counter but don't add any delays
 	if (vdp_port >= 4 && vdp_port < 8 && v_context->cycles != before_cycle) {
-		refresh_counter = 0;
+		gen->refresh_counter = 0;
 	} else {
-		refresh_counter += (context->current_cycle - last_sync_cycle);
-		refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
+		gen->refresh_counter += (context->current_cycle - gen->last_sync_cycle);
+		gen->refresh_counter = gen->refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
 	}
-	last_sync_cycle = context->current_cycle;
-#endif
+	gen->last_sync_cycle = context->current_cycle;
 	return context;
 }
 
@@ -746,13 +761,13 @@
 	}
 	vdp_port &= 0x1F;
 	uint16_t value;
-#ifdef REFRESH_EMULATION
+
 	//do refresh check here so we can avoid adding a penalty for a refresh that happens during a VDP access
-	refresh_counter += context->current_cycle - 4*MCLKS_PER_68K - last_sync_cycle;
-	context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL));
-	refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
-	last_sync_cycle = context->current_cycle;
-#endif
+	gen->refresh_counter += context->current_cycle - 4*MCLKS_PER_68K - gen->last_sync_cycle;
+	context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (gen->refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL));
+	gen->refresh_counter = gen->refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
+	gen->last_sync_cycle = context->current_cycle;
+
 	sync_components(context, 0);
 	vdp_context * v_context = gen->vdp;
 	uint32_t before_cycle = context->current_cycle;
@@ -778,14 +793,13 @@
 		sync_z80(gen, context->current_cycle);
 		gen->bus_busy = 0;
 	}
-#ifdef REFRESH_EMULATION
-	last_sync_cycle -= 4 * MCLKS_PER_68K;
+
+	gen->last_sync_cycle -= 4 * MCLKS_PER_68K;
 	//refresh may have happened while we were waiting on the VDP,
 	//so advance refresh_counter but don't add any delays
-	refresh_counter += (context->current_cycle - last_sync_cycle);
-	refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
-	last_sync_cycle = context->current_cycle;
-#endif
+	gen->refresh_counter += (context->current_cycle - gen->last_sync_cycle);
+	gen->refresh_counter = gen->refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
+	gen->last_sync_cycle = context->current_cycle;
 	return value;
 }
 
@@ -844,13 +858,13 @@
 static m68k_context * io_write(uint32_t location, m68k_context * context, uint8_t value)
 {
 	genesis_context * gen = context->system;
-#ifdef REFRESH_EMULATION
+
 	//do refresh check here so we can avoid adding a penalty for a refresh that happens during an IO area access
-	refresh_counter += context->current_cycle - 4*MCLKS_PER_68K - last_sync_cycle;
-	context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL));
-	refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
-	last_sync_cycle = context->current_cycle - 4*MCLKS_PER_68K;
-#endif
+	gen->refresh_counter += context->current_cycle - 4*MCLKS_PER_68K - gen->last_sync_cycle;
+	context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (gen->refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL));
+	gen->refresh_counter = gen->refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
+	gen->last_sync_cycle = context->current_cycle - 4*MCLKS_PER_68K;
+
 	if (location < 0x10000) {
 		//Access to Z80 memory incurs a one 68K cycle wait state
 		context->current_cycle += MCLKS_PER_68K;
@@ -976,11 +990,10 @@
 			}
 		}
 	}
-#ifdef REFRESH_EMULATION
+
 	//no refresh delays during IO access
-	refresh_counter += context->current_cycle - last_sync_cycle;
-	refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
-#endif
+	gen->refresh_counter += context->current_cycle - gen->last_sync_cycle;
+	gen->refresh_counter = gen->refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
 	return context;
 }
 
@@ -1004,13 +1017,13 @@
 {
 	uint8_t value;
 	genesis_context *gen = context->system;
-#ifdef REFRESH_EMULATION
+
 	//do refresh check here so we can avoid adding a penalty for a refresh that happens during an IO area access
-	refresh_counter += context->current_cycle - 4*MCLKS_PER_68K - last_sync_cycle;
-	context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL));
-	refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
-	last_sync_cycle = context->current_cycle - 4*MCLKS_PER_68K;
-#endif
+	gen->refresh_counter += context->current_cycle - 4*MCLKS_PER_68K - gen->last_sync_cycle;
+	context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (gen->refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL));
+	gen->refresh_counter = gen->refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
+	gen->last_sync_cycle = context->current_cycle - 4*MCLKS_PER_68K;
+
 	if (location < 0x10000) {
 		//Access to Z80 memory incurs a one 68K cycle wait state
 		context->current_cycle += MCLKS_PER_68K;
@@ -1106,12 +1119,11 @@
 			}
 		}
 	}
-#ifdef REFRESH_EMULATION
+
 	//no refresh delays during IO access
-	refresh_counter += context->current_cycle - last_sync_cycle;
-	refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
-	last_sync_cycle = context->current_cycle;
-#endif
+	gen->refresh_counter += context->current_cycle - gen->last_sync_cycle;
+	gen->refresh_counter = gen->refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
+	gen->last_sync_cycle = context->current_cycle;
 	return value;
 }
 
@@ -1224,16 +1236,14 @@
 {
 	m68k_context *context = vcontext;
 	genesis_context *gen = context->system;
-#ifdef REFRESH_EMULATION
 	if (location >= 0x800000) {
 		//do refresh check here so we can avoid adding a penalty for a refresh that happens during an IO area access
-		refresh_counter += context->current_cycle - 4*MCLKS_PER_68K - last_sync_cycle;
-		context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL));
-		refresh_counter += 4*MCLKS_PER_68K;
-		refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
-		last_sync_cycle = context->current_cycle;
+		gen->refresh_counter += context->current_cycle - 4*MCLKS_PER_68K - gen->last_sync_cycle;
+		context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (gen->refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL));
+		gen->refresh_counter += 4*MCLKS_PER_68K;
+		gen->refresh_counter = gen->refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
+		gen->last_sync_cycle = context->current_cycle;
 	}
-#endif
 
 	if (location < 0x800000 || (location >= 0xA13000 && location < 0xA13100) || (location >= 0xA12000 && location < 0xA12100)) {
 		//Only called if the cart/exp doesn't have a more specific handler for this region
@@ -1289,16 +1299,15 @@
 {
 	m68k_context *context = vcontext;
 	genesis_context *gen = context->system;
-#ifdef REFRESH_EMULATION
 	if (location >= 0x800000) {
 		//do refresh check here so we can avoid adding a penalty for a refresh that happens during an IO area access
-		refresh_counter += context->current_cycle - 4*MCLKS_PER_68K - last_sync_cycle;
-		context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL));
-		refresh_counter += 4*MCLKS_PER_68K;
-		refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
-		last_sync_cycle = context->current_cycle;
+		gen->refresh_counter += context->current_cycle - 4*MCLKS_PER_68K - gen->last_sync_cycle;
+		context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (gen->refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL));
+		gen->refresh_counter += 4*MCLKS_PER_68K;
+		gen->refresh_counter = gen->refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL);
+		gen->last_sync_cycle = context->current_cycle;
 	}
-#endif
+
 	uint8_t has_tmss = gen->version_reg & 0xF;
 	if (has_tmss && (location == 0xA14000 || location == 0xA14002)) {
 		gen->tmss_lock[location >> 1 & 1] = value;
@@ -1412,8 +1421,6 @@
 	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");
@@ -1421,7 +1428,7 @@
 		ret = pc != 0;
 	}
 	if (ret) {
-		gen->m68k->resume_pc = get_native_address_trans(gen->m68k, pc);
+		debug_message("Loaded state from %s\n", statepath);
 	}
 done:
 	free(statepath);