changeset 2280:9ead0fe69d9b

Implement savestate support for Sega CD
author Michael Pavone <pavone@retrodev.com>
date Sun, 08 Jan 2023 14:42:24 -0800
parents 3b5fef896475
children b9fed07f19e4
files cdd_fader.c cdd_fader.h cdd_mcu.c cdd_mcu.h cdimage.c cdimage.h genesis.c genesis.h gst.c lc8951.c lc8951.h rf5c164.c rf5c164.h segacd.c segacd.h serialize.h
diffstat 16 files changed, 503 insertions(+), 100 deletions(-) [+]
line wrap: on
line diff
--- a/cdd_fader.c	Sun Jan 08 14:20:43 2023 -0800
+++ b/cdd_fader.c	Sun Jan 08 14:42:24 2023 -0800
@@ -76,3 +76,24 @@
 		}
 	}
 }
+
+void cdd_fader_serialize(cdd_fader *fader, serialize_buffer *buf)
+{
+	save_int16(buf, fader->cur_attenuation);
+	save_int16(buf, fader->dst_attenuation);
+	save_int16(buf, fader->attenuation_step);
+	save_int8(buf, fader->flags);
+	save_buffer8(buf, fader->bytes, sizeof(fader->bytes));
+	save_int8(buf, fader->byte_counter);
+}
+
+void cdd_fader_deserialize(deserialize_buffer *buf, void *vfader)
+{
+	cdd_fader *fader = vfader;
+	fader->cur_attenuation = load_int16(buf);
+	fader->dst_attenuation = load_int16(buf);
+	fader->attenuation_step = load_int16(buf);
+	fader->flags = load_int8(buf);
+	load_buffer8(buf, fader->bytes, sizeof(fader->bytes));
+	fader->byte_counter = load_int8(buf);
+}
--- a/cdd_fader.h	Sun Jan 08 14:20:43 2023 -0800
+++ b/cdd_fader.h	Sun Jan 08 14:42:24 2023 -0800
@@ -2,6 +2,7 @@
 #define CDD_FADER_H_
 
 #include "render_audio.h"
+#include "serialize.h"
 
 typedef struct {
 	audio_source *audio;
@@ -19,5 +20,7 @@
 void cdd_fader_attenuation_write(cdd_fader *fader, uint16_t attenuation);
 void cdd_fader_data(cdd_fader *fader, uint8_t byte);
 void cdd_fader_pause(cdd_fader *fader);
+void cdd_fader_serialize(cdd_fader *fader, serialize_buffer *buf);
+void cdd_fader_deserialize(deserialize_buffer *buf, void *vfader);
 
 #endif //CDD_FADER_H_
--- a/cdd_mcu.c	Sun Jan 08 14:20:43 2023 -0800
+++ b/cdd_mcu.c	Sun Jan 08 14:42:24 2023 -0800
@@ -809,3 +809,90 @@
 		context->next_subcode_cycle -= cd_deduction;
 	}
 }
+
+void cdd_mcu_serialize(cdd_mcu *context, serialize_buffer *buf)
+{
+	save_int32(buf, context->cycle);
+	save_int32(buf, context->next_int_cycle);
+	save_int32(buf, context->next_subcode_int_cycle);
+	save_int32(buf, context->last_sector_cycle);
+	save_int32(buf, context->last_nibble_cycle);
+	save_int32(buf, context->next_byte_cycle);
+	save_int32(buf, context->next_subcode_cycle);
+	save_int8(buf, context->current_status_nibble);
+	save_int8(buf, context->current_cmd_nibble);
+	save_int16(buf, context->current_sector_byte);
+	save_int8(buf, context->current_subcode_byte);
+	save_int8(buf, context->current_subcode_dest);
+	save_int32(buf, context->head_pba);
+	save_int32(buf, context->seek_pba);
+	save_int32(buf, context->pause_pba);
+	save_int32(buf, context->coarse_seek);
+	save_buffer8(buf, (uint8_t *)&context->cmd_buffer, sizeof(context->cmd_buffer));
+	save_buffer8(buf, (uint8_t *)&context->status_buffer, sizeof(context->status_buffer));
+	save_int8(buf, context->requested_format);
+	save_int8(buf, context->status);
+	save_int8(buf, context->error_status);
+	save_int8(buf, context->requested_track);
+	save_int8(buf, context->cmd_recv_wait);
+	save_int8(buf, context->cmd_recv_pending);
+	save_int8(buf, context->int_pending);
+	save_int8(buf, context->subcode_int_pending);
+	save_int8(buf, context->toc_valid);
+	save_int8(buf, context->first_cmd_received);
+	save_int8(buf, context->seeking);
+	save_int8(buf, context->in_fake_pregap);
+}
+
+static int sign_extend8(uint8_t value)
+{
+	if (value & 0x80) {
+		return value | 0xFFFFFF00;
+	} else {
+		return value;
+	}
+}
+
+static int sign_extend16(uint16_t value)
+{
+	if (value & 0x8000) {
+		return value | 0xFFFF0000;
+	} else {
+		return value;
+	}
+}
+
+void cdd_mcu_deserialize(deserialize_buffer *buf, void *vcontext)
+{
+	cdd_mcu *context = vcontext;
+	context->cycle = load_int32(buf);
+	context->next_int_cycle = load_int32(buf);
+	context->next_subcode_int_cycle = load_int32(buf);
+	context->last_sector_cycle = load_int32(buf);
+	context->last_nibble_cycle = load_int32(buf);
+	context->next_byte_cycle = load_int32(buf);
+	context->next_subcode_cycle = load_int32(buf);
+	context->current_status_nibble = sign_extend8(load_int8(buf));
+	context->current_cmd_nibble = sign_extend8(load_int8(buf));
+	context->current_sector_byte = sign_extend16(load_int16(buf));
+	context->current_subcode_byte = sign_extend8(load_int8(buf));
+	context->current_subcode_dest = sign_extend8(load_int8(buf));
+	context->head_pba = load_int32(buf);
+	context->seek_pba = load_int32(buf);
+	context->pause_pba = load_int32(buf);
+	context->coarse_seek = load_int32(buf);
+	load_buffer8(buf, (uint8_t *)&context->cmd_buffer, sizeof(context->cmd_buffer));
+	load_buffer8(buf, (uint8_t *)&context->status_buffer, sizeof(context->status_buffer));
+	context->requested_format = load_int8(buf);
+	context->status = load_int8(buf);
+	context->error_status = load_int8(buf);
+	context->requested_track = load_int8(buf);
+	context->cmd_recv_wait = load_int8(buf);
+	context->cmd_recv_pending = load_int8(buf);
+	context->int_pending = load_int8(buf);
+	context->subcode_int_pending = load_int8(buf);
+	context->toc_valid = load_int8(buf);
+	context->first_cmd_received = load_int8(buf);
+	context->seeking = load_int8(buf);
+	context->in_fake_pregap = load_int8(buf);
+}
--- a/cdd_mcu.h	Sun Jan 08 14:20:43 2023 -0800
+++ b/cdd_mcu.h	Sun Jan 08 14:42:24 2023 -0800
@@ -3,6 +3,7 @@
 #include "system.h"
 #include "lc8951.h"
 #include "cdd_fader.h"
+#include "serialize.h"
 
 typedef enum {
 	SF_ABSOLUTE,
@@ -172,5 +173,7 @@
 void cdd_hock_disabled(cdd_mcu *context);
 void cdd_mcu_start_cmd_recv(cdd_mcu *context, uint16_t *gate_array);
 void cdd_mcu_adjust_cycle(cdd_mcu *context, uint32_t deduction);
+void cdd_mcu_serialize(cdd_mcu *context, serialize_buffer *buf);
+void cdd_mcu_deserialize(deserialize_buffer *buf, void *vcontext);
 
 #endif //CD_MCU_H_
--- a/cdimage.c	Sun Jan 08 14:20:43 2023 -0800
+++ b/cdimage.c	Sun Jan 08 14:42:24 2023 -0800
@@ -561,3 +561,41 @@
 	media->read_subcodes = bin_subcode_read;
 	return media->size;
 }
+
+void cdimage_serialize(system_media *media, serialize_buffer *buf)
+{
+	if (media->type != MEDIA_CDROM) {
+		return;
+	}
+	save_int32(buf, media->cur_track);
+	save_int32(buf, media->cur_sector);
+	if (media->cur_track < media->num_tracks && media->tracks[media->cur_track].f) {
+		save_int32(buf, ftell(media->tracks[media->cur_track].f));
+	} else {
+		save_int32(buf, 0);
+	}
+	save_int8(buf, media->in_fake_pregap);
+	save_int8(buf, media->byte_storage);
+	if (media->tmp_buffer) {
+		save_buffer8(buf, media->tmp_buffer, 96);
+	}
+}
+
+void cdimage_deserialize(deserialize_buffer *buf, void *vmedia)
+{
+	system_media *media = vmedia;
+	if (media->type != MEDIA_CDROM) {
+		return;
+	}
+	media->cur_track = load_int32(buf);
+	media->cur_sector = load_int32(buf);
+	uint32_t seekpos = load_int32(buf);
+	if (media->cur_track < media->num_tracks && media->tracks[media->cur_track].f) {
+		fseek(media->tracks[media->cur_track].f, seekpos, SEEK_SET);
+	}
+	media->in_fake_pregap = load_int8(buf);
+	media->byte_storage = load_int8(buf);
+	if (media->tmp_buffer) {
+		load_buffer8(buf, media->tmp_buffer, 96);
+	}
+}
--- a/cdimage.h	Sun Jan 08 14:20:43 2023 -0800
+++ b/cdimage.h	Sun Jan 08 14:42:24 2023 -0800
@@ -4,5 +4,7 @@
 uint8_t parse_cue(system_media *media);
 uint8_t parse_toc(system_media *media);
 uint32_t make_iso_media(system_media *media, const char *filename);
+void cdimage_serialize(system_media *media, serialize_buffer *buf);
+void cdimage_deserialize(deserialize_buffer *buf, void *vmedia);
 
 #endif //CUE_H_
--- 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);
--- a/genesis.h	Sun Jan 08 14:20:43 2023 -0800
+++ b/genesis.h	Sun Jan 08 14:42:24 2023 -0800
@@ -62,6 +62,8 @@
 	uint32_t        last_flush_cycle;
 	uint32_t        soft_flush_cycles;
 	uint32_t        tmss_write_offset;
+	uint32_t        last_sync_cycle;
+	uint32_t        refresh_counter;
 	uint16_t        z80_bank_reg;
 	uint16_t        tmss_lock[2];
 	uint16_t        mapper_start_index;
--- a/gst.c	Sun Jan 08 14:20:43 2023 -0800
+++ b/gst.c	Sun Jan 08 14:42:24 2023 -0800
@@ -412,6 +412,7 @@
 	return 1;
 }
 
+#include "m68k_internal.h" //needed for get_native_address_trans, should be eliminated once handling of PC is cleaned up
 uint32_t load_gst(genesis_context * gen, char * fname)
 {
 	char buffer[4096];
@@ -461,6 +462,7 @@
 			i++;
 		}
 	}
+	gen->m68k->resume_pc = get_native_address_trans(gen->m68k, pc);
 	fclose(gstfile);
 	return pc;
 
--- a/lc8951.c	Sun Jan 08 14:20:43 2023 -0800
+++ b/lc8951.c	Sun Jan 08 14:42:24 2023 -0800
@@ -428,3 +428,48 @@
 	}
 	printf("cycle is now %u, decode_end %u, transfer_end %u\n", context->cycle, context->decode_end, context->transfer_end);
 }
+
+void lc8951_serialize(lc8951 *context, serialize_buffer *buf)
+{
+	save_int32(buf, context->cycle);
+	save_int32(buf, context->decode_end);
+	save_int32(buf, context->transfer_end);
+	save_int32(buf, context->next_byte_cycle);
+	save_int16(buf, context->sector_counter);
+	save_buffer8(buf, context->buffer, sizeof(context->buffer));
+	save_buffer8(buf, context->regs, sizeof(context->regs));
+	save_buffer8(buf, context->comin, sizeof(context->comin));
+	save_int16(buf, context->dac);
+	save_int8(buf, context->comin_write);
+	save_int8(buf, context->comin_count);
+	save_int8(buf, context->ifctrl);
+	save_int8(buf, context->ctrl0);
+	save_int8(buf, context->ctrl1);
+	save_int8(buf, context->ar);
+	save_int8(buf, context->ar_mask);
+	save_int8(buf, context->triggered);
+	save_int8(buf, context->sync_counter);
+}
+
+void lc8951_deserialize(deserialize_buffer *buf, void *vcontext)
+{
+	lc8951 *context = vcontext;
+	context->cycle = load_int32(buf);
+	context->decode_end = load_int32(buf);
+	context->transfer_end = load_int32(buf);
+	context->next_byte_cycle = load_int32(buf);
+	context->sector_counter = load_int16(buf);
+	load_buffer8(buf, context->buffer, sizeof(context->buffer));
+	load_buffer8(buf, context->regs, sizeof(context->regs));
+	load_buffer8(buf, context->comin, sizeof(context->comin));
+	context->dac = load_int16(buf);
+	context->comin_write = load_int8(buf);
+	context->comin_count = load_int8(buf);
+	context->ifctrl = load_int8(buf);
+	context->ctrl0 = load_int8(buf);
+	context->ctrl1 = load_int8(buf);
+	context->ar = load_int8(buf);
+	context->ar_mask = load_int8(buf);
+	context->triggered = load_int8(buf);
+	context->sync_counter = load_int8(buf);
+}
--- a/lc8951.h	Sun Jan 08 14:20:43 2023 -0800
+++ b/lc8951.h	Sun Jan 08 14:42:24 2023 -0800
@@ -2,6 +2,7 @@
 #define LC8951_H_
 
 #include <stdint.h>
+#include "serialize.h"
 
 typedef uint8_t (*lcd8951_byte_recv_fun)(void *data, uint8_t byte);
 
@@ -43,5 +44,7 @@
 uint32_t lc8951_next_interrupt(lc8951 *context);
 void lc8951_resume_transfer(lc8951 *context, uint32_t cycle);
 void lc8951_adjust_cycles(lc8951 *context, uint32_t deduction);
+void lc8951_serialize(lc8951 *context, serialize_buffer *buf);
+void lc8951_deserialize(deserialize_buffer *buf, void *vcontext);
 
 #endif //LC8951_H_
--- a/rf5c164.c	Sun Jan 08 14:20:43 2023 -0800
+++ b/rf5c164.c	Sun Jan 08 14:42:24 2023 -0800
@@ -262,3 +262,50 @@
 		pcm->channels[i].scope_channel = scope_add_channel(scope, names[i], 50000000 / (pcm->clock_step * 96));
 	}
 }
+
+void rf5c164_serialize(rf5c164* pcm, serialize_buffer *buf)
+{
+	save_int32(buf, pcm->cycle);
+	save_buffer16(buf, pcm->ram, sizeof(pcm->ram)/sizeof(*pcm->ram));
+	save_int16(buf, pcm->ram_bank);
+	save_int16(buf, pcm->pending_address);
+	save_int32(buf, pcm->left);
+	save_int32(buf, pcm->right);
+	for (int i = 0; i < 8; i++)
+	{
+		rf5c164_channel *chan = pcm->channels + i;
+		save_int32(buf, chan->cur_ptr);
+		save_buffer8(buf, chan->regs, sizeof(chan->regs));
+		save_int8(buf, chan->sample);
+	}
+	save_int8(buf, pcm->pending_byte);
+	save_int8(buf, pcm->channel_enable);
+	save_int8(buf, pcm->selected_channel);
+	save_int8(buf, pcm->cur_channel);
+	save_int8(buf, pcm->step);
+	save_int8(buf, pcm->flags);
+}
+
+void rf5c164_deserialize(deserialize_buffer *buf, void *vpcm)
+{
+	rf5c164 *pcm = vpcm;
+	pcm->cycle = load_int32(buf);
+	load_buffer16(buf, pcm->ram, sizeof(pcm->ram)/sizeof(*pcm->ram));
+	pcm->ram_bank = load_int16(buf);
+	pcm->pending_address = load_int16(buf);
+	pcm->left = load_int32(buf);
+	pcm->right = load_int32(buf);
+	for (int i = 0; i < 8; i++)
+	{
+		rf5c164_channel *chan = pcm->channels + i;
+		chan->cur_ptr = load_int32(buf);
+		load_buffer8(buf, chan->regs, sizeof(chan->regs));
+		chan->sample = load_int8(buf);
+	}
+	pcm->pending_byte = load_int8(buf);
+	pcm->channel_enable = load_int8(buf);
+	pcm->selected_channel = load_int8(buf);
+	pcm->cur_channel = load_int8(buf);
+	pcm->step = load_int8(buf);
+	pcm->flags = load_int8(buf);
+}
--- a/rf5c164.h	Sun Jan 08 14:20:43 2023 -0800
+++ b/rf5c164.h	Sun Jan 08 14:42:24 2023 -0800
@@ -3,6 +3,7 @@
 #include <stdint.h>
 #include "render_audio.h"
 #include "oscilloscope.h"
+#include "serialize.h"
 
 typedef struct {
 	uint32_t cur_ptr;
@@ -39,5 +40,7 @@
 void rf5c164_write(rf5c164* pcm, uint16_t address, uint8_t value);
 uint8_t rf5c164_read(rf5c164* pcm, uint16_t address);
 void rf5c164_enable_scope(rf5c164* pcm, oscilloscope *scope);
+void rf5c164_serialize(rf5c164* pcm, serialize_buffer *buf);
+void rf5c164_deserialize(deserialize_buffer *buf, void *vpcm);
 
 #endif //RF5C164_H_
--- a/segacd.c	Sun Jan 08 14:20:43 2023 -0800
+++ b/segacd.c	Sun Jan 08 14:42:24 2023 -0800
@@ -7,6 +7,7 @@
 #include "debug.h"
 #include "gdb_remote.h"
 #include "blastem.h"
+#include "cdimage.h"
 
 #define SCD_MCLKS 50000000
 #define SCD_PERIPH_RESET_CLKS (SCD_MCLKS / 10)
@@ -1057,14 +1058,17 @@
 	context->current_cycle += num_refresh * REFRESH_DELAY;
 
 	scd_peripherals_run(cd, context->current_cycle);
-	if (address && cd->enter_debugger) {
-		genesis_context *gen = cd->genesis;
-		cd->enter_debugger = 0;
-		if (gen->header.debugger_type == DEBUGGER_NATIVE) {
-			debugger(context, address);
-		} else {
-			gdb_debug_enter(context, address);
+	if (address) {
+		if (cd->enter_debugger) {
+			genesis_context *gen = cd->genesis;
+			cd->enter_debugger = 0;
+			if (gen->header.debugger_type == DEBUGGER_NATIVE) {
+				debugger(context, address);
+			} else {
+				gdb_debug_enter(context, address);
+			}
 		}
+		cd->m68k_pc = address;
 	}
 	switch (context->int_ack)
 	{
@@ -1453,14 +1457,8 @@
 	memcpy(cd->rom_mut, cd->rom, adjusted_size);
 	cd->rom_mut[0x72/2] = 0xFFFF;
 
-	//memset(info, 0, sizeof(*info));
-	//tern_node *db = get_rom_db();
-	//*info = configure_rom(db, media->buffer, media->size, media->chain ? media->chain->buffer : NULL, media->chain ? media->chain->size : 0, NULL, 0);
-
 	cd->prog_ram = calloc(512*1024, 1);
 	cd->word_ram = calloc(256*1024, 1);
-	cd->pcm_ram = calloc(64*1024, 1);
-	//TODO: Load state from file
 	cd->bram = calloc(8*1024, 1);
 
 
@@ -1497,12 +1495,144 @@
 	m68k_options_free(cd->m68k->options);
 	free(cd->m68k);
 	free(cd->bram);
-	free(cd->pcm_ram);
 	free(cd->word_ram);
 	free(cd->prog_ram);
 	free(cd->rom_mut);
 }
 
+void segacd_serialize(segacd_context *cd, serialize_buffer *buf, uint8_t all)
+{
+	if (all) {
+		start_section(buf, SECTION_SUB_68000);
+		m68k_serialize(cd->m68k, cd->m68k_pc, buf);
+		end_section(buf);
+
+		start_section(buf, SECTION_GATE_ARRAY);
+		save_buffer16(buf, cd->gate_array, sizeof(cd->gate_array)/sizeof(*cd->gate_array));
+		save_buffer16(buf, cd->prog_ram, 256*1024);
+		save_buffer16(buf, cd->word_ram, 128*1024);
+		save_int16(buf, cd->rom_mut[0x72/2]);
+		save_int32(buf, cd->stopwatch_cycle);
+		save_int32(buf, cd->int2_cycle);
+		save_int32(buf, cd->graphics_int_cycle);
+		save_int32(buf, cd->periph_reset_cycle);
+		save_int32(buf, cd->last_refresh_cycle);
+		save_int32(buf, cd->graphics_cycle);
+		save_int32(buf, cd->base);
+		save_int32(buf, cd->graphics_x);
+		save_int32(buf, cd->graphics_y);
+		save_int32(buf, cd->graphics_dx);
+		save_int32(buf, cd->graphics_dy);
+		save_int32(buf, cd->graphics_dst_x);
+		save_buffer8(buf, cd->graphics_pixels, sizeof(cd->graphics_pixels));
+		save_int8(buf, cd->timer_pending);
+		save_int8(buf, cd->timer_value);
+		save_int8(buf, cd->busreq);
+		save_int8(buf, cd->reset);
+		save_int8(buf, cd->need_reset);
+		save_int8(buf, cd->cdc_dst_low);
+		save_int8(buf, cd->cdc_int_ack);
+		save_int8(buf, cd->graphics_step);
+		save_int8(buf, cd->graphics_dst_y);
+		save_int8(buf, cd->main_has_word2m);
+		save_int8(buf, cd->main_swap_request);
+		save_int8(buf, cd->bank_toggle);
+		save_int8(buf, cd->sub_paused_wordram);
+		end_section(buf);
+
+		start_section(buf, SECTION_CDD_MCU);
+		cdd_mcu_serialize(&cd->cdd, buf);
+		end_section(buf);
+
+		start_section(buf, SECTION_LC8951);
+		lc8951_serialize(&cd->cdc, buf);
+		end_section(buf);
+
+		start_section(buf, SECTION_CDROM);
+		cdimage_serialize(cd->cdd.media, buf);
+		end_section(buf);
+	}
+	start_section(buf, SECTION_RF5C164);
+	rf5c164_serialize(&cd->pcm, buf);
+	end_section(buf);
+
+	start_section(buf, SECTION_CDD_FADER);
+	cdd_fader_serialize(&cd->fader, buf);
+	end_section(buf);
+}
+
+static void gate_array_deserialize(deserialize_buffer *buf, void *vcd)
+{
+	segacd_context *cd = vcd;
+	load_buffer16(buf, cd->gate_array, sizeof(cd->gate_array)/sizeof(*cd->gate_array));
+	load_buffer16(buf, cd->prog_ram, 256*1024);
+	load_buffer16(buf, cd->word_ram, 128*1024);
+	cd->rom_mut[0x72/2] = load_int16(buf);
+	cd->stopwatch_cycle = load_int32(buf);
+	cd->int2_cycle = load_int32(buf);
+	cd->graphics_int_cycle = load_int32(buf);
+	cd->periph_reset_cycle = load_int32(buf);
+	cd->last_refresh_cycle = load_int32(buf);
+	cd->graphics_cycle = load_int32(buf);
+	cd->base = load_int32(buf);
+	cd->graphics_x = load_int32(buf);
+	cd->graphics_y = load_int32(buf);
+	cd->graphics_dx = load_int32(buf);
+	cd->graphics_dy = load_int32(buf);
+	cd->graphics_dst_x = load_int32(buf);
+	load_buffer8(buf, cd->graphics_pixels, sizeof(cd->graphics_pixels));
+	cd->timer_pending = load_int8(buf);
+	cd->timer_value = load_int8(buf);
+	cd->busreq = load_int8(buf);
+	cd->reset = load_int8(buf);
+	cd->need_reset = load_int8(buf);
+	cd->cdc_dst_low = load_int8(buf);
+	cd->cdc_int_ack = load_int8(buf);
+	cd->graphics_step = load_int8(buf);
+	cd->graphics_dst_y = load_int8(buf);
+	cd->main_has_word2m = load_int8(buf);
+	cd->main_swap_request = load_int8(buf);
+	cd->bank_toggle = load_int8(buf);
+	cd->sub_paused_wordram = load_int8(buf);
+
+	if (cd->gate_array[GA_MEM_MODE] & BIT_MEM_MODE) {
+		//1M mode
+		cd->genesis->m68k->mem_pointers[cd->memptr_start_index + 1] = NULL;
+		cd->genesis->m68k->mem_pointers[cd->memptr_start_index + 2] = NULL;
+		cd->m68k->mem_pointers[0] = NULL;
+		cd->m68k->mem_pointers[1] = cd->bank_toggle ? cd->word_ram : cd->word_ram + 1;
+	} else {
+		//2M mode
+		if (cd->main_has_word2m) {
+			//main CPU has word ram
+			cd->genesis->m68k->mem_pointers[cd->memptr_start_index + 1] = cd->word_ram;
+			cd->genesis->m68k->mem_pointers[cd->memptr_start_index + 2] = cd->word_ram + 0x10000;
+			cd->m68k->mem_pointers[0] = NULL;
+			cd->m68k->mem_pointers[1] = NULL;
+		} else {
+			//sub cpu has word ram
+			cd->genesis->m68k->mem_pointers[cd->memptr_start_index + 1] = NULL;
+			cd->genesis->m68k->mem_pointers[cd->memptr_start_index + 2] = NULL;
+			cd->m68k->mem_pointers[0] = cd->word_ram;
+			cd->m68k->mem_pointers[1] = NULL;
+		}
+	}
+
+	m68k_invalidate_code_range(cd->m68k, 0, 0x0E0000);
+	m68k_invalidate_code_range(cd->genesis->m68k, cd->base + 0x200000, cd->base + 0x240000);
+}
+
+void segacd_register_section_handlers(segacd_context *cd, deserialize_buffer *buf)
+{
+	register_section_handler(buf, (section_handler){.fun = m68k_deserialize, .data = cd->m68k}, SECTION_SUB_68000);
+	register_section_handler(buf, (section_handler){.fun = gate_array_deserialize, .data = cd}, SECTION_GATE_ARRAY);
+	register_section_handler(buf, (section_handler){.fun = cdd_mcu_deserialize, .data = &cd->cdd}, SECTION_CDD_MCU);
+	register_section_handler(buf, (section_handler){.fun = lc8951_deserialize, .data = &cd->cdc}, SECTION_LC8951);
+	register_section_handler(buf, (section_handler){.fun = rf5c164_deserialize, .data = &cd->pcm}, SECTION_RF5C164);
+	register_section_handler(buf, (section_handler){.fun = cdd_fader_deserialize, .data = &cd->fader}, SECTION_CDD_FADER);
+	register_section_handler(buf, (section_handler){.fun = cdimage_deserialize, .data = cd->cdd.media}, SECTION_CDROM);
+}
+
 memmap_chunk *segacd_main_cpu_map(segacd_context *cd, uint8_t cart_boot, uint32_t *num_chunks)
 {
 	static memmap_chunk main_cpu_map[] = {
--- a/segacd.h	Sun Jan 08 14:20:43 2023 -0800
+++ b/segacd.h	Sun Jan 08 14:42:24 2023 -0800
@@ -4,6 +4,7 @@
 #include "genesis.h"
 #include "cdd_mcu.h"
 #include "rf5c164.h"
+#include "serialize.h"
 
 typedef struct {
 	m68k_context    *m68k;
@@ -14,7 +15,6 @@
 	uint16_t        *rom_mut; //ROM with low 16-bit of HINT vector modified by register write
 	uint16_t        *prog_ram;
 	uint16_t        *word_ram;
-	uint8_t         *pcm_ram;
 	uint8_t         *bram;
 	uint32_t        stopwatch_cycle;
 	uint32_t        int2_cycle;
@@ -23,6 +23,7 @@
 	uint32_t        last_refresh_cycle;
 	uint32_t        graphics_cycle;
 	uint32_t        base;
+	uint32_t        m68k_pc;
 	uint32_t        graphics_x;
 	uint32_t        graphics_y;
 	uint32_t        graphics_dx;
@@ -37,10 +38,6 @@
 	uint8_t         reset;
 	uint8_t         need_reset;
 	uint8_t         memptr_start_index;
-	rf5c164         pcm;
-	lc8951          cdc;
-	cdd_mcu         cdd;
-	cdd_fader       fader;
 	uint8_t         cdc_dst_low;
 	uint8_t         cdc_int_ack;
 	uint8_t         graphics_step;
@@ -50,6 +47,10 @@
 	uint8_t         main_swap_request;
 	uint8_t         bank_toggle;
 	uint8_t         sub_paused_wordram;
+	rf5c164         pcm;
+	lc8951          cdc;
+	cdd_mcu         cdd;
+	cdd_fader       fader;
 } segacd_context;
 
 segacd_context *alloc_configure_segacd(system_media *media, uint32_t opts, uint8_t force_region, rom_info *info);
@@ -60,5 +61,7 @@
 void scd_adjust_cycle(segacd_context *cd, uint32_t deduction);
 void scd_toggle_graphics_debug(segacd_context *cd);
 void segacd_set_speed_percent(segacd_context *cd, uint32_t percent);
+void segacd_serialize(segacd_context *cd, serialize_buffer *buf, uint8_t all);
+void segacd_register_section_handlers(segacd_context *cd, deserialize_buffer *buf);
 
 #endif //SEGACD_H_
--- a/serialize.h	Sun Jan 08 14:20:43 2023 -0800
+++ b/serialize.h	Sun Jan 08 14:42:24 2023 -0800
@@ -43,7 +43,14 @@
 	SECTION_MAPPER,
 	SECTION_EEPROM,
 	SECTION_CART_RAM,
-	SECTION_TMSS
+	SECTION_TMSS,
+	SECTION_SUB_68000,
+	SECTION_GATE_ARRAY,
+	SECTION_CDD_MCU,
+	SECTION_LC8951,
+	SECTION_RF5C164,
+	SECTION_CDD_FADER,
+	SECTION_CDROM
 };
 
 void init_serialize(serialize_buffer *buf);