changeset 1427:4e5797b3935a

WIP - New savestate format
author Michael Pavone <pavone@retrodev.com>
date Sun, 06 Aug 2017 00:06:36 -0700
parents 957325c990d5
children 2540c05520f2
files Makefile genesis.c io.c io.h m68k_core.c m68k_core.h menu.c psg.c psg.h serialize.c serialize.h vdp.c vdp.h ym2612.c ym2612.h z80_to_x86.c z80_to_x86.h
diffstat 17 files changed, 1049 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Fri Jul 07 21:44:49 2017 -0700
+++ b/Makefile	Sun Aug 06 00:06:36 2017 -0700
@@ -127,7 +127,7 @@
 AUDIOOBJS=ym2612.o psg.o wave.o
 CONFIGOBJS=config.o tern.o util.o
 
-MAINOBJS=blastem.o system.o genesis.o debug.o gdb_remote.o vdp.o render_sdl.o ppm.o io.o romdb.o hash.o menu.o xband.o realtec.o i2c.o nor.o sega_mapper.o multi_game.o $(TERMINAL) $(CONFIGOBJS) gst.o $(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS)
+MAINOBJS=blastem.o system.o genesis.o debug.o gdb_remote.o vdp.o render_sdl.o ppm.o io.o romdb.o hash.o menu.o xband.o realtec.o i2c.o nor.o sega_mapper.o multi_game.o serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o $(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS)
 
 ifeq ($(CPU),x86_64)
 CFLAGS+=-DX86_64 -m64
@@ -162,7 +162,7 @@
 	$(CC) -o $@ $^ $(LDFLAGS)
 	$(FIXUP) ./$@
 	
-blastjag$(EXE) : jaguar.o jag_video.o render_sdl.o $(M68KOBJS) $(TRANSOBJS) $(CONFIGOBJS)
+blastjag$(EXE) : jaguar.o jag_video.o render_sdl.o serialize.o $(M68KOBJS) $(TRANSOBJS) $(CONFIGOBJS)
 	$(CC) -o $@ $^ $(LDFLAGS)
 
 dis$(EXE) : dis.o 68kinst.o tern.o vos_program_module.o
@@ -177,27 +177,27 @@
 libemu68k.a : $(M68KOBJS) $(TRANSOBJS)
 	ar rcs libemu68k.a $(M68KOBJS) $(TRANSOBJS)
 
-trans : trans.o $(M68KOBJS) $(TRANSOBJS) util.o
+trans : trans.o serialize.o $(M68KOBJS) $(TRANSOBJS) util.o
 	$(CC) -o trans trans.o $(M68KOBJS) $(TRANSOBJS) util.o
 
 transz80 : transz80.o $(Z80OBJS) $(TRANSOBJS)
 	$(CC) -o transz80 transz80.o $(Z80OBJS) $(TRANSOBJS)
 
-ztestrun : ztestrun.o $(Z80OBJS) $(TRANSOBJS)
+ztestrun : ztestrun.o serialize.o $(Z80OBJS) $(TRANSOBJS)
 	$(CC) -o ztestrun ztestrun.o $(Z80OBJS) $(TRANSOBJS)
 
 ztestgen : ztestgen.o z80inst.o
 	$(CC) -ggdb -o ztestgen ztestgen.o z80inst.o
 
-stateview$(EXE) : stateview.o vdp.o render_sdl.o ppm.o $(CONFIGOBJS) gst.o
+stateview$(EXE) : stateview.o vdp.o render_sdl.o ppm.o serialize.o $(CONFIGOBJS) gst.o
 	$(CC) -o $@ $^ $(LDFLAGS)
 	$(FIXUP) ./$@
 
-vgmplay$(EXE) : vgmplay.o render_sdl.o ppm.o $(CONFIGOBJS) $(AUDIOOBJS)
+vgmplay$(EXE) : vgmplay.o render_sdl.o ppm.o serialize.o $(CONFIGOBJS) $(AUDIOOBJS)
 	$(CC) -o $@ $^ $(LDFLAGS)
 	$(FIXUP) ./$@
 
-blastcpm : blastcpm.o util.o $(Z80OBJS) $(TRANSOBJS)
+blastcpm : blastcpm.o util.o serialize.o $(Z80OBJS) $(TRANSOBJS)
 	$(CC) -o $@ $^
 
 test : test.o vdp.o
--- a/genesis.c	Fri Jul 07 21:44:49 2017 -0700
+++ b/genesis.c	Sun Aug 06 00:06:36 2017 -0700
@@ -32,6 +32,98 @@
 
 #define MAX_SOUND_CYCLES 100000	
 
+void genesis_serialize(genesis_context *gen, serialize_buffer *buf, uint32_t m68k_pc)
+{
+	start_section(buf, SECTION_68000);
+	m68k_serialize(gen->m68k, m68k_pc, buf);
+	end_section(buf);
+	
+	start_section(buf, SECTION_Z80);
+	z80_serialize(gen->z80, buf);
+	end_section(buf);
+	
+	start_section(buf, SECTION_VDP);
+	vdp_serialize(gen->vdp, buf);
+	end_section(buf);
+	
+	start_section(buf, SECTION_YM2612);
+	ym_serialize(gen->ym, buf);
+	end_section(buf);
+	
+	start_section(buf, SECTION_PSG);
+	psg_serialize(gen->psg, buf);
+	end_section(buf);
+	
+	//TODO: bus arbiter state
+	
+	start_section(buf, SECTION_SEGA_IO_1);
+	io_serialize(gen->io.ports, buf);
+	end_section(buf);
+	
+	start_section(buf, SECTION_SEGA_IO_2);
+	io_serialize(gen->io.ports + 1, buf);
+	end_section(buf);
+	
+	start_section(buf, SECTION_SEGA_IO_EXT);
+	io_serialize(gen->io.ports + 2, buf);
+	end_section(buf);
+	
+	start_section(buf, SECTION_MAIN_RAM);
+	save_int8(buf, RAM_WORDS * 2 / 1024);
+	save_buffer16(buf, gen->work_ram, RAM_WORDS);
+	end_section(buf);
+	
+	start_section(buf, SECTION_SOUND_RAM);
+	save_int8(buf, Z80_RAM_BYTES / 1024);
+	save_buffer8(buf, gen->zram, Z80_RAM_BYTES);
+	end_section(buf);
+	
+	//TODO: mapper state
+}
+
+static void ram_deserialize(deserialize_buffer *buf, void *vgen)
+{
+	genesis_context *gen = vgen;
+	uint32_t ram_size = load_int8(buf) * 1024 / 2;
+	if (ram_size > RAM_WORDS) {
+		fatal_error("State has a RAM size of %d bytes", ram_size * 2);
+	}
+	load_buffer16(buf, gen->work_ram, ram_size);
+}
+
+static void zram_deserialize(deserialize_buffer *buf, void *vgen)
+{
+	genesis_context *gen = vgen;
+	uint32_t ram_size = load_int8(buf) * 1024;
+	if (ram_size > Z80_RAM_BYTES) {
+		fatal_error("State has a Z80 RAM size of %d bytes", ram_size);
+	}
+	load_buffer8(buf, gen->zram, ram_size);
+}
+
+void genesis_deserialize(deserialize_buffer *buf, genesis_context *gen)
+{
+	register_section_handler(buf, (section_handler){.fun = m68k_deserialize, .data = gen->m68k}, SECTION_68000);
+	register_section_handler(buf, (section_handler){.fun = z80_deserialize, .data = gen->z80}, SECTION_Z80);
+	register_section_handler(buf, (section_handler){.fun = vdp_deserialize, .data = gen->vdp}, SECTION_VDP);
+	register_section_handler(buf, (section_handler){.fun = ym_deserialize, .data = gen->ym}, SECTION_YM2612);
+	register_section_handler(buf, (section_handler){.fun = psg_deserialize, .data = gen->psg}, SECTION_PSG);
+	//TODO: bus arbiter
+	//HACK
+	gen->z80->reset = 0;
+	gen->z80->busreq = 0;
+	register_section_handler(buf, (section_handler){.fun = io_deserialize, .data = gen->io.ports}, SECTION_SEGA_IO_1);
+	register_section_handler(buf, (section_handler){.fun = io_deserialize, .data = gen->io.ports + 1}, SECTION_SEGA_IO_2);
+	register_section_handler(buf, (section_handler){.fun = io_deserialize, .data = gen->io.ports + 2}, SECTION_SEGA_IO_EXT);
+	register_section_handler(buf, (section_handler){.fun = ram_deserialize, .data = gen}, SECTION_MAIN_RAM);
+	register_section_handler(buf, (section_handler){.fun = zram_deserialize, .data = gen}, SECTION_SOUND_RAM);
+	//TODO: mapper state
+	while (buf->cur_pos < buf->size)
+	{
+		load_section(buf);
+	}
+}
+
 uint16_t read_dma_value(uint32_t address)
 {
 	genesis_context *genesis = (genesis_context *)current_system;
@@ -252,7 +344,14 @@
 				char const *parts[] = {gen->header.save_dir, PATH_SEP, slotname};
 				save_path = alloc_concat_m(3, parts);
 			}
-			save_gst(gen, save_path, address);
+			serialize_buffer state;
+			init_serialize(&state);
+			genesis_serialize(gen, &state, address);;
+			FILE *statefile = fopen(save_path, "wb");
+			fwrite(state.data, 1, state.size, statefile);
+			fclose(statefile);
+			free(state.data);
+			//save_gst(gen, save_path, address);
 			printf("Saved state to %s\n", save_path);
 			if (slot != QUICK_SAVE_SLOT) {
 				free(save_path);
@@ -906,9 +1005,26 @@
 	set_keybindings(&gen->io);
 	render_set_video_standard((gen->version_reg & HZ50) ? VID_PAL : VID_NTSC);
 	if (statefile) {
+		//first try loading as a GST format savestate
 		uint32_t pc = load_gst(gen, statefile);
 		if (!pc) {
-			fatal_error("Failed to load save state %s\n", statefile);
+			//switch to native format if that fails
+			FILE *f = fopen(statefile, "rb");
+			if (!f) {
+				goto state_error;
+			}
+			long statesize = file_size(f);
+			deserialize_buffer state;
+			void *statedata = malloc(statesize);
+			if (statesize != fread(statedata, 1, statesize, f)) {
+				goto state_error;
+			}
+			fclose(f);
+			init_deserialize(&state, statedata, statesize);
+			genesis_deserialize(&state, gen);
+			free(statedata);
+			//HACK
+			pc = gen->m68k->last_prefetch_address;
 		}
 		printf("Loaded %s\n", statefile);
 		if (gen->header.enter_debugger) {
@@ -926,6 +1042,9 @@
 		m68k_reset(gen->m68k);
 	}
 	handle_reset_requests(gen);
+	return;
+state_error:
+	fatal_error("Failed to load save state %s\n", statefile);
 }
 
 static void resume_genesis(system_header *system)
--- a/io.c	Fri Jul 07 21:44:49 2017 -0700
+++ b/io.c	Sun Aug 06 00:06:36 2017 -0700
@@ -15,6 +15,7 @@
 #include <string.h>
 #include <stdlib.h>
 
+#include "serialize.h"
 #include "io.h"
 #include "blastem.h"
 #include "genesis.h"
@@ -2130,4 +2131,74 @@
 	return value;
 }
 
+void io_serialize(io_port *port, serialize_buffer *buf)
+{
+	save_int8(buf, port->output);
+	save_int8(buf, port->control);
+	save_int8(buf, port->serial_out);
+	save_int8(buf, port->serial_in);
+	save_int8(buf, port->serial_ctrl);
+	save_int8(buf, port->device_type);
+	save_buffer32(buf, port->slow_rise_start, 8);
+	switch (port->device_type)
+	{
+	case IO_GAMEPAD6:
+		save_int32(buf, port->device.pad.timeout_cycle);
+		save_int16(buf, port->device.pad.th_counter);
+		break;
+	case IO_MOUSE:
+		save_int32(buf, port->device.mouse.ready_cycle);
+		save_int16(buf, port->device.mouse.last_read_x);
+		save_int16(buf, port->device.mouse.last_read_y);
+		save_int16(buf, port->device.mouse.latched_x);
+		save_int16(buf, port->device.mouse.latched_y);
+		save_int8(buf, port->device.mouse.tr_counter);
+		break;
+	case IO_SATURN_KEYBOARD:
+	case IO_XBAND_KEYBOARD:
+		save_int8(buf, port->device.keyboard.tr_counter);
+		if (port->device_type == IO_XBAND_KEYBOARD) {
+			save_int8(buf, port->device.keyboard.mode);
+			save_int8(buf, port->device.keyboard.cmd);
+		}
+		break;
+	}
+}
 
+void io_deserialize(deserialize_buffer *buf, void *vport)
+{
+	io_port *port = vport;
+	port->output = load_int8(buf);
+	port->control = load_int8(buf);
+	port->serial_out = load_int8(buf);
+	port->serial_in = load_int8(buf);
+	port->serial_ctrl = load_int8(buf);
+	uint8_t device_type = load_int8(buf);
+	if (device_type != port->device_type) {
+		warning("Loaded save state has a different device type from the current configuration");
+		return;
+	}
+	switch (port->device_type)
+	{
+	case IO_GAMEPAD6:
+		port->device.pad.timeout_cycle = load_int32(buf);
+		port->device.pad.th_counter = load_int16(buf);
+		break;
+	case IO_MOUSE:
+		port->device.mouse.ready_cycle = load_int32(buf);
+		port->device.mouse.last_read_x = load_int16(buf);
+		port->device.mouse.last_read_y = load_int16(buf);
+		port->device.mouse.latched_x = load_int16(buf);
+		port->device.mouse.latched_y = load_int16(buf);
+		port->device.mouse.tr_counter = load_int8(buf);
+		break;
+	case IO_SATURN_KEYBOARD:
+	case IO_XBAND_KEYBOARD:
+		port->device.keyboard.tr_counter = load_int8(buf);
+		if (port->device_type == IO_XBAND_KEYBOARD) {
+			port->device.keyboard.mode = load_int8(buf);
+			port->device.keyboard.cmd = load_int8(buf);
+		}
+		break;
+	}
+}
--- a/io.h	Fri Jul 07 21:44:49 2017 -0700
+++ b/io.h	Sun Aug 06 00:06:36 2017 -0700
@@ -8,6 +8,7 @@
 #include <stdint.h>
 #include "tern.h"
 #include "romdb.h"
+#include "serialize.h"
 
 enum {
 	IO_GAMEPAD2,
@@ -109,6 +110,8 @@
 void handle_mouse_moved(int mouse, uint16_t x, uint16_t y, int16_t deltax, int16_t deltay);
 void handle_mousedown(int mouse, int button);
 void handle_mouseup(int mouse, int button);
+void io_serialize(io_port *port, serialize_buffer *buf);
+void io_deserialize(deserialize_buffer *buf, void *vport);
 
 #endif //IO_H_
 
--- a/m68k_core.c	Fri Jul 07 21:44:49 2017 -0700
+++ b/m68k_core.c	Sun Aug 06 00:06:36 2017 -0700
@@ -9,6 +9,7 @@
 #include "backend.h"
 #include "gen.h"
 #include "util.h"
+#include "serialize.h"
 #include <stdio.h>
 #include <stddef.h>
 #include <stdlib.h>
@@ -1202,3 +1203,54 @@
 	context->reset_handler = (code_ptr)reset_handler;
 	return context;
 }
+
+void m68k_serialize(m68k_context *context, uint32_t pc, serialize_buffer *buf)
+{
+	for (int i = 0; i < 8; i++)
+	{
+		save_int32(buf, context->dregs[i]);
+	}
+	for (int i = 0; i < 9; i++)
+	{
+		save_int32(buf, context->aregs[i]);
+	}
+	save_int32(buf, pc);
+	uint16_t sr = context->status << 3;
+	for (int flag = 4; flag >= 0; flag--) {
+		sr <<= 1;
+		sr |= context->flags[flag] != 0;
+	}
+	save_int16(buf, sr);
+	save_int32(buf, context->current_cycle);
+	save_int32(buf, context->int_cycle);
+	save_int8(buf, context->int_num);
+	save_int8(buf, context->int_pending);
+	save_int8(buf, context->trace_pending);
+}
+
+void m68k_deserialize(deserialize_buffer *buf, void *vcontext)
+{
+	m68k_context *context = vcontext;
+	for (int i = 0; i < 8; i++)
+	{
+		context->dregs[i] = load_int32(buf);
+	}
+	for (int i = 0; i < 9; i++)
+	{
+		context->aregs[i] = load_int32(buf);
+	}
+	//hack until both PC and IR registers are represented properly
+	context->last_prefetch_address = load_int32(buf);
+	uint16_t sr = load_int16(buf);
+	context->status = sr >> 8;
+	for (int flag = 0; flag < 5; flag++)
+	{
+		context->flags[flag] = sr & 1;
+		sr >>= 1;
+	}
+	context->current_cycle = load_int32(buf);
+	context->int_cycle = load_int32(buf);
+	context->int_num = load_int8(buf);
+	context->int_pending = load_int8(buf);
+	context->trace_pending = load_int8(buf);
+}
--- a/m68k_core.h	Fri Jul 07 21:44:49 2017 -0700
+++ b/m68k_core.h	Sun Aug 06 00:06:36 2017 -0700
@@ -8,6 +8,7 @@
 #include <stdint.h>
 #include <stdio.h>
 #include "backend.h"
+#include "serialize.h"
 //#include "68kinst.h"
 struct m68kinst;
 
@@ -116,6 +117,8 @@
 uint16_t m68k_get_ir(m68k_context *context);
 void m68k_print_regs(m68k_context * context);
 void m68k_invalidate_code_range(m68k_context *context, uint32_t start, uint32_t end);
+void m68k_serialize(m68k_context *context, uint32_t pc, serialize_buffer *buf);
+void m68k_deserialize(deserialize_buffer *buf, void *vcontext);
 
 #endif //M68K_CORE_H_
 
--- a/menu.c	Fri Jul 07 21:44:49 2017 -0700
+++ b/menu.c	Sun Aug 06 00:06:36 2017 -0700
@@ -346,8 +346,10 @@
 			char *cur = buffer;
 			if (gen->header.next_context && gen->header.next_context->save_dir) {
 				char *end = buffer + SAVE_INFO_BUFFER_SIZE;
-				char slotfile[] = "slot_0.gst";
+				char slotfile[] = "slot_0.state";
+				char slotfilegst[] = "slot_0.gst";
 				char const * parts[3] = {gen->header.next_context->save_dir, PATH_SEP, slotfile};
+				char const * partsgst[3] = {gen->header.next_context->save_dir, PATH_SEP, slotfilegst};
 				struct tm ltime;
 				char *fname;
 				time_t modtime;
@@ -362,20 +364,37 @@
 						cur += strftime(cur, end-cur, "%c", localtime_r(&modtime, &ltime));
 						
 					} else {
-						cur += snprintf(cur, end-cur, "Slot %d - EMPTY", i);
+						slotfilegst[5] = i + '0';
+						fname = alloc_concat_m(3, partsgst);
+						modtime = get_modification_time(fname);
+						free(fname);
+						if (modtime) {
+							cur += snprintf(cur, end-cur, "Slot %d - ", i);
+							cur += strftime(cur, end-cur, "%c", localtime_r(&modtime, &ltime));
+						} else {
+							cur += snprintf(cur, end-cur, "Slot %d - EMPTY", i);
+						}
 					}
 					//advance past the null terminator for this entry
 					cur++;
 				}
 				if (cur < end) {
-					parts[2] = "quicksave.gst";
+					parts[2] = "quicksave.state";
 					fname = alloc_concat_m(3, parts);
 					modtime = get_modification_time(fname);
 					free(fname);
 					if (modtime) {
 						cur += strftime(cur, end-cur, "Quick  - %c", localtime_r(&modtime, &ltime));
-					} else if ((end-cur) > strlen("Quick  - EMPTY")){
-						cur += strlen(strcpy(cur, "Quick  - EMPTY"));
+					} else {
+						parts[2] = "quicksave.gst";
+						fname = alloc_concat_m(3, parts);
+						modtime = get_modification_time(fname);
+						free(fname);
+						if (modtime) {
+							cur += strftime(cur, end-cur, "Quick  - %c", localtime_r(&modtime, &ltime));
+						} else if ((end-cur) > strlen("Quick  - EMPTY")){
+							cur += strlen(strcpy(cur, "Quick  - EMPTY"));
+						}
 					}
 					//advance past the null terminator for this entry
 					cur++;
@@ -401,10 +420,10 @@
 		case 6:
 			//load state
 			if (gen->header.next_context && gen->header.next_context->save_dir) {
-				char numslotname[] = "slot_0.gst";
+				char numslotname[] = "slot_0.state";
 				char *slotname;
 				if (dst == QUICK_SAVE_SLOT) {
-					slotname = "quicksave.gst";
+					slotname = "quicksave.state";
 				} else {
 					numslotname[5] = '0' + dst;
 					slotname = numslotname;
@@ -412,6 +431,7 @@
 				char const *parts[] = {gen->header.next_context->save_dir, PATH_SEP, slotname};
 				char *gstpath = alloc_concat_m(3, parts);
 				genesis_context *next = (genesis_context *)gen->header.next_context;
+				
 				uint32_t pc = load_gst(next, gstpath);
 				free(gstpath);
 				if (!pc) {
--- a/psg.c	Fri Jul 07 21:44:49 2017 -0700
+++ b/psg.c	Sun Aug 06 00:06:36 2017 -0700
@@ -151,3 +151,35 @@
 	}
 }
 
+void psg_serialize(psg_context *context, serialize_buffer *buf)
+{
+	save_int16(buf, context->lsfr);
+	save_buffer16(buf, context->counter_load, 4);
+	save_buffer16(buf, context->counters, 4);
+	save_buffer8(buf, context->volume, 4);
+	uint8_t output_state = context->output_state[0] << 3 | context->output_state[1] << 2
+		| context->output_state[2] << 1 | context->output_state[3]
+		| context->noise_use_tone << 4;
+	save_int8(buf, output_state);
+	save_int8(buf, context->noise_type);
+	save_int8(buf, context->latch);
+	save_int32(buf, context->cycles);
+}
+
+void psg_deserialize(deserialize_buffer *buf, void *vcontext)
+{
+	psg_context *context = vcontext;
+	context->lsfr = load_int16(buf);
+	load_buffer16(buf, context->counter_load, 4);
+	load_buffer16(buf, context->counters, 4);
+	load_buffer8(buf, context->volume, 4);
+	uint8_t output_state = load_int8(buf);
+	context->output_state[0] = output_state & 8 >> 3;
+	context->output_state[1] = output_state & 4 >> 2;
+	context->output_state[2] = output_state & 2 >> 1;
+	context->output_state[3] = output_state & 1;
+	context->noise_use_tone = output_state & 0x10 >> 4;
+	context->noise_type = load_int8(buf);
+	context->latch = load_int8(buf);
+	context->cycles = load_int32(buf);
+}
--- a/psg.h	Fri Jul 07 21:44:49 2017 -0700
+++ b/psg.h	Sun Aug 06 00:06:36 2017 -0700
@@ -7,6 +7,7 @@
 #define PSG_CONTEXT_H_
 
 #include <stdint.h>
+#include "serialize.h"
 
 typedef struct {
 	int16_t  *audio_buffer;
@@ -38,6 +39,8 @@
 void psg_adjust_master_clock(psg_context * context, uint32_t master_clock);
 void psg_write(psg_context * context, uint8_t value);
 void psg_run(psg_context * context, uint32_t cycles);
+void psg_serialize(psg_context *context, serialize_buffer *buf);
+void psg_deserialize(deserialize_buffer *buf, void *vcontext);
 
 #endif //PSG_CONTEXT_H_
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/serialize.c	Sun Aug 06 00:06:36 2017 -0700
@@ -0,0 +1,276 @@
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "serialize.h"
+#include "util.h"
+
+#ifndef SERIALIZE_DEFAULT_SIZE
+#define SERIALIZE_DEFAULT_SIZE (256*1024) //default to enough for a Genesis save state
+#endif
+
+
+void init_serialize(serialize_buffer *buf)
+{
+	buf->storage = SERIALIZE_DEFAULT_SIZE;
+	buf->size = 0;
+	buf->current_section_start = 0;
+	buf->data = malloc(SERIALIZE_DEFAULT_SIZE);
+}
+
+static void reserve(serialize_buffer *buf, size_t amount)
+{
+	if (amount > (buf->storage - buf->size)) {
+		buf->storage *= 2;
+		buf = realloc(buf, buf->storage + sizeof(*buf));
+	}
+}
+
+void save_int32(serialize_buffer *buf, uint32_t val)
+{
+	reserve(buf, sizeof(val));
+	buf->data[buf->size++] = val >> 24;
+	buf->data[buf->size++] = val >> 16;
+	buf->data[buf->size++] = val >> 8;
+	buf->data[buf->size++] = val;
+}
+
+void save_int16(serialize_buffer *buf, uint16_t val)
+{
+	reserve(buf, sizeof(val));
+	buf->data[buf->size++] = val >> 8;
+	buf->data[buf->size++] = val;
+}
+
+void save_int8(serialize_buffer *buf, uint8_t val)
+{
+	reserve(buf, sizeof(val));
+	buf->data[buf->size++] = val;
+}
+
+void save_string(serialize_buffer *buf, char *val)
+{
+	size_t len = strlen(val);
+	save_buffer8(buf, val, len);
+}
+
+void save_buffer8(serialize_buffer *buf, void *val, size_t len)
+{
+	reserve(buf, len);
+	memcpy(&buf->data[buf->size], val, len);
+	buf->size += len;
+}
+
+void save_buffer16(serialize_buffer *buf, uint16_t *val, size_t len)
+{
+	reserve(buf, len * sizeof(*val));
+	for(; len != 0; len--, val++) {
+		buf->data[buf->size++] = *val >> 8;
+		buf->data[buf->size++] = *val;
+	}
+}
+
+void save_buffer32(serialize_buffer *buf, uint32_t *val, size_t len)
+{
+	reserve(buf, len * sizeof(*val));
+	for(; len != 0; len--, val++) {
+		buf->data[buf->size++] = *val >> 24;
+		buf->data[buf->size++] = *val >> 16;
+		buf->data[buf->size++] = *val >> 8;
+		buf->data[buf->size++] = *val;
+	}
+}
+
+void start_section(serialize_buffer *buf, uint16_t section_id)
+{
+	save_int16(buf, section_id);
+	//reserve some space for size once we end this section
+	reserve(buf, sizeof(uint32_t));
+	buf->size += sizeof(uint32_t);
+	//save start point for use in end_device
+	buf->current_section_start = buf->size;
+}
+
+void end_section(serialize_buffer *buf)
+{
+	size_t section_size = buf->size - buf->current_section_start;
+	if (section_size > 0xFFFFFFFFU) {
+		fatal_error("Sections larger than 4GB are not supported");
+	}
+	uint32_t size = section_size;
+	uint8_t *field = buf->data + buf->current_section_start - sizeof(uint32_t);
+	*(field++) = size >> 24;
+	*(field++) = size >> 16;
+	*(field++) = size >> 8;
+	*(field++) = size;
+	buf->current_section_start = 0;
+}
+
+void register_section_handler(deserialize_buffer *buf, section_handler handler, uint16_t section_id)
+{
+	if (section_id > buf->max_handler) {
+		uint16_t old_max = buf->max_handler;
+		if (buf->max_handler < 0x8000) {
+			buf->max_handler *= 2;
+		} else {
+			buf->max_handler = 0xFFFF;
+		}
+		buf->handlers = realloc(buf->handlers, (buf->max_handler+1) * sizeof(handler));
+		memset(buf->handlers + old_max + 1, 0, (buf->max_handler - old_max) * sizeof(handler));
+	}
+	if (!buf->handlers) {
+		buf->handlers = calloc(buf->max_handler + 1, sizeof(handler));
+	}
+	buf->handlers[section_id] = handler;
+}
+
+void init_deserialize(deserialize_buffer *buf, uint8_t *data, size_t size)
+{
+	buf->size = size;
+	buf->cur_pos = 0;
+	buf->data = data;
+	buf->handlers = NULL;
+	buf->max_handler = 8;
+}
+
+uint32_t load_int32(deserialize_buffer *buf)
+{
+	uint32_t val;
+	if ((buf->size - buf->cur_pos) < sizeof(val)) {
+		fatal_error("Failed to load required int32 field");
+	}
+	val = buf->data[buf->cur_pos++] << 24;
+	val |= buf->data[buf->cur_pos++] << 16;
+	val |= buf->data[buf->cur_pos++] << 8;
+	val |= buf->data[buf->cur_pos++];
+	return val;
+}
+
+uint16_t load_int16(deserialize_buffer *buf)
+{
+	uint16_t val;
+	if ((buf->size - buf->cur_pos) < sizeof(val)) {
+		fatal_error("Failed to load required int16 field");
+	}
+	val = buf->data[buf->cur_pos++] << 8;
+	val |= buf->data[buf->cur_pos++];
+	return val;
+}
+
+uint8_t load_int8(deserialize_buffer *buf)
+{
+	uint8_t val;
+	if ((buf->size - buf->cur_pos) < sizeof(val)) {
+		fatal_error("Failed to load required int8 field");
+	}
+	val = buf->data[buf->cur_pos++];
+	return val;
+}
+
+void load_buffer8(deserialize_buffer *buf, void *dst, size_t len)
+{
+	if ((buf->size - buf->cur_pos) < len) {
+		fatal_error("Failed to load required buffer of size %d", len);
+	}
+	memcpy(dst, buf->data + buf->cur_pos, len);
+	buf->cur_pos += len;
+}
+
+void load_buffer16(deserialize_buffer *buf, uint16_t *dst, size_t len)
+{
+	if ((buf->size - buf->cur_pos) < len * sizeof(uint16_t)) {
+		fatal_error("Failed to load required buffer of size %d\n", len);
+	}
+	for(; len != 0; len--, dst++) {
+		uint16_t value = buf->data[buf->cur_pos++] << 8;
+		value |= buf->data[buf->cur_pos++];
+		*dst = value;
+	}
+}
+void load_buffer32(deserialize_buffer *buf, uint32_t *dst, size_t len)
+{
+	if ((buf->size - buf->cur_pos) < len * sizeof(uint32_t)) {
+		fatal_error("Failed to load required buffer of size %d\n", len);
+	}
+	for(; len != 0; len--, dst++) {
+		uint32_t value = buf->data[buf->cur_pos++] << 24;
+		value |= buf->data[buf->cur_pos++] << 16;
+		value |= buf->data[buf->cur_pos++] << 8;
+		value |= buf->data[buf->cur_pos++];
+		*dst = value;
+	}
+}
+
+void load_section(deserialize_buffer *buf)
+{
+	if (!buf->handlers) {
+		fatal_error("load_section called on a deserialize_buffer with no handlers registered\n");
+	}
+	uint16_t section_id = load_int16(buf);
+	uint32_t size = load_int32(buf);
+	if (size > (buf->size - buf->cur_pos)) {
+		fatal_error("Section is bigger than remaining space in file");
+	}
+	if (section_id > buf->max_handler || !buf->handlers[section_id].fun) {
+		warning("No handler for section ID %d, save state may be from a newer version\n", section_id);
+		buf->cur_pos += size;
+		return;
+	}
+	deserialize_buffer section;
+	init_deserialize(&section, buf->data + buf->cur_pos, size);
+	buf->handlers[section_id].fun(&section, buf->handlers[section_id].data);
+	buf->cur_pos += size;
+}
+
+static const char sz_ident[] = "BLSTSZ\x01\x07";
+
+uint8_t save_to_file(serialize_buffer *buf, char *path)
+{
+	FILE *f = fopen(path, "wb");
+	if (!f) {
+		return 0;
+	}
+	if (fwrite(sz_ident, 1, sizeof(sz_ident)-1, f) != sizeof(sz_ident)-1) {
+		fclose(f);
+		return 0;
+	}
+	if (fwrite(buf->data, 1, buf->size, f) != buf->size) {
+		fclose(f);
+		return 0;
+	}
+	fclose(f);
+	return 1;
+}
+
+uint8_t load_from_file(deserialize_buffer *buf, char *path)
+{
+	FILE *f = fopen(path, "rb");
+	if (!f) {
+		return 0;
+	}
+	char ident[sizeof(sz_ident)-1];
+	long size = file_size(f);
+	if (size < sizeof(ident)) {
+		fclose(f);
+		return 0;
+	}
+	if (fread(ident, 1, sizeof(ident), f) != sizeof(ident)) {
+		fclose(f);
+		return 0;
+	}
+	fclose(f);
+	if (memcmp(ident, sz_ident, sizeof(ident))) {
+		return 0;
+	}
+	buf->size = size - sizeof(ident);
+	buf->cur_pos = 0;
+	buf->data = malloc(buf->size);
+	buf->handlers = NULL;
+	buf->max_handler = 8;
+	if (fread(buf->data, 1, buf->size, f) != buf->size) {
+		free(buf->data);
+		buf->data = NULL;
+		buf->size = 0;
+		return 0;
+	}
+	return 1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/serialize.h	Sun Aug 06 00:06:36 2017 -0700
@@ -0,0 +1,69 @@
+#ifndef SERIALIZE_H_
+#define SERIALIZE_H_
+
+#include <stdint.h>
+#include <stddef.h>
+
+typedef struct {
+	size_t  size;
+	size_t  storage;
+	size_t  current_section_start;
+	uint8_t *data;
+} serialize_buffer;
+
+typedef struct deserialize_buffer deserialize_buffer;
+typedef void (*section_fun)(deserialize_buffer *buf, void *data);
+
+typedef struct  {
+	section_fun fun;
+	void        *data;
+} section_handler;
+
+struct deserialize_buffer {
+	size_t          size;
+	size_t          cur_pos;
+	uint8_t         *data;
+	section_handler *handlers;
+	uint16_t        max_handler;
+};
+
+enum {
+	SECTION_HEADER,
+	SECTION_68000,
+	SECTION_Z80,
+	SECTION_VDP,
+	SECTION_YM2612,
+	SECTION_PSG,
+	SECTION_GEN_BUS_ARBITER,
+	SECTION_SEGA_IO_1,
+	SECTION_SEGA_IO_2,
+	SECTION_SEGA_IO_EXT,
+	SECTION_MAIN_RAM,
+	SECTION_SOUND_RAM,
+	SECTION_SEGA_MAPPER,
+	SECTION_SMS_MAPPER,
+	SECTION_EEPROM
+};
+
+void init_serialize(serialize_buffer *buf);
+void save_int32(serialize_buffer *buf, uint32_t val);
+void save_int16(serialize_buffer *buf, uint16_t val);
+void save_int8(serialize_buffer *buf, uint8_t val);
+void save_string(serialize_buffer *buf, char *val);
+void save_buffer8(serialize_buffer *buf, void *val, size_t len);
+void save_buffer16(serialize_buffer *buf, uint16_t *val, size_t len);
+void save_buffer32(serialize_buffer *buf, uint32_t *val, size_t len);
+void start_section(serialize_buffer *buf, uint16_t section_id);
+void end_section(serialize_buffer *buf);
+void register_section_handler(deserialize_buffer *buf, section_handler handler, uint16_t section_id);
+void init_deserialize(deserialize_buffer *buf, uint8_t *data, size_t size);
+uint32_t load_int32(deserialize_buffer *buf);
+uint16_t load_int16(deserialize_buffer *buf);
+uint8_t load_int8(deserialize_buffer *buf);
+void load_buffer8(deserialize_buffer *buf, void *dst, size_t len);
+void load_buffer16(deserialize_buffer *buf, uint16_t *dst, size_t len);
+void load_buffer32(deserialize_buffer *buf, uint32_t *dst, size_t len);
+void load_section(deserialize_buffer *buf);
+uint8_t save_to_file(serialize_buffer *buf, char *path);
+uint8_t load_from_file(deserialize_buffer *buf, char *path);
+#endif //SERIALIZE_H
--- a/vdp.c	Fri Jul 07 21:44:49 2017 -0700
+++ b/vdp.c	Sun Aug 06 00:06:36 2017 -0700
@@ -2996,7 +2996,6 @@
 			//
 			if((context->regs[REG_DMASRC_H] & 0xC0) != 0x80) {
 				//DMA copy or 68K -> VDP, transfer starts immediately
-				context->dma_cd = context->cd;
 				//printf("DMA start (length: %X) at cycle %d, frame: %d, vcounter: %d, hslot: %d\n", (context->regs[REG_DMALEN_H] << 8) | context->regs[REG_DMALEN_L], context->cycles, context->frame, context->vcounter, context->hslot);
 				if (!(context->regs[REG_DMASRC_H] & 0x80)) {
 					//printf("DMA Address: %X, New CD: %X, Source: %X, Length: %X\n", context->address, context->cd, (context->regs[REG_DMASRC_H] << 17) | (context->regs[REG_DMASRC_M] << 9) | (context->regs[REG_DMASRC_L] << 1), context->regs[REG_DMALEN_H] << 8 | context->regs[REG_DMALEN_L]);
@@ -3526,3 +3525,164 @@
 	}
 }
 
+void vdp_serialize(vdp_context *context, serialize_buffer *buf)
+{
+	save_int8(buf, VRAM_SIZE / 1024);//VRAM size in KB, needed for future proofing
+	save_buffer8(buf, context->vdpmem, VRAM_SIZE);
+	save_buffer16(buf, context->cram, CRAM_SIZE);
+	save_buffer16(buf, context->vsram, VSRAM_SIZE);
+	save_buffer8(buf, context->sat_cache, SAT_CACHE_SIZE);
+	for (int i = 0; i <= REG_DMASRC_H; i++)
+	{
+		save_int8(buf, context->regs[i]);
+	}
+	save_int32(buf, context->address);
+	save_int32(buf, context->serial_address);
+	save_int8(buf, context->cd);
+	uint8_t fifo_size;
+	if (context->fifo_read < 0) {
+		fifo_size = 0;
+	} else if (context->fifo_write > context->fifo_read) {
+		fifo_size = context->fifo_write - context->fifo_read;
+	} else {
+		fifo_size = context->fifo_write + FIFO_SIZE - context->fifo_read;
+	}
+	save_int8(buf, fifo_size);
+	for (int i = 0, cur = context->fifo_read; i < fifo_size; i++)
+	{
+		fifo_entry *entry = context->fifo + cur;
+		cur = (cur + 1) & (FIFO_SIZE - 1);
+		save_int32(buf, entry->cycle);
+		save_int32(buf, entry->address);
+		save_int16(buf, entry->value);
+		save_int8(buf, entry->cd);
+		save_int8(buf, entry->partial);
+	}
+	//FIXME: Flag bits should be rearranged for maximum correspondence to status reg
+	save_int16(buf, context->flags2 << 8 | context->flags);
+	save_int32(buf, context->frame);
+	save_int16(buf, context->vcounter);
+	save_int8(buf, context->hslot);
+	save_int16(buf, context->hv_latch);
+	save_int8(buf, context->state);
+	save_int16(buf, context->hscroll_a);
+	save_int16(buf, context->hscroll_b);
+	save_int16(buf, context->vscroll_latch[0]);
+	save_int16(buf, context->vscroll_latch[1]);
+	save_int16(buf, context->col_1);
+	save_int16(buf, context->col_2);
+	save_int16(buf, context->test_port);
+	save_buffer8(buf, context->tmp_buf_a, SCROLL_BUFFER_SIZE);
+	save_buffer8(buf, context->tmp_buf_b, SCROLL_BUFFER_SIZE);
+	save_int8(buf, context->buf_a_off);
+	save_int8(buf, context->buf_b_off);
+	//FIXME: Sprite rendering state is currently a mess
+	save_int8(buf, context->sprite_index);
+	save_int8(buf, context->sprite_draws);
+	save_int8(buf, context->slot_counter);
+	save_int8(buf, context->cur_slot);
+	for (int i = 0; i < MAX_DRAWS; i++)
+	{
+		sprite_draw *draw = context->sprite_draw_list + i;
+		save_int16(buf, draw->address);
+		save_int16(buf, draw->x_pos);
+		save_int8(buf, draw->pal_priority);
+		save_int8(buf, draw->h_flip);
+	}
+	for (int i = 0; i < MAX_SPRITES_LINE; i++)
+	{
+		sprite_info *info = context->sprite_info_list + i;
+		save_int8(buf, info->size);
+		save_int8(buf, info->index);
+		save_int16(buf, info->y);
+	}
+	save_buffer8(buf, context->linebuf, LINEBUF_SIZE);
+	
+	save_int32(buf, context->cycles);
+	save_int32(buf, context->pending_vint_start);
+	save_int32(buf, context->pending_hint_start);
+}
+
+void vdp_deserialize(deserialize_buffer *buf, void *vcontext)
+{
+	vdp_context *context = vcontext;
+	uint8_t vramk = load_int8(buf);
+	load_buffer8(buf, context->vdpmem, (vramk * 1024) <= VRAM_SIZE ? vramk * 1024 : VRAM_SIZE);
+	if ((vramk * 1024) > VRAM_SIZE) {
+		buf->cur_pos += (vramk * 1024) - VRAM_SIZE;
+	}
+	load_buffer16(buf, context->cram, CRAM_SIZE);
+	load_buffer16(buf, context->vsram, VSRAM_SIZE);
+	load_buffer8(buf, context->sat_cache, SAT_CACHE_SIZE);
+	for (int i = 0; i <= REG_DMASRC_H; i++)
+	{
+		context->regs[i] = load_int8(buf);
+	}
+	context->address = load_int32(buf);
+	context->serial_address = load_int32(buf);
+	context->cd = load_int8(buf);
+	uint8_t fifo_size = load_int8(buf);
+	if (fifo_size > FIFO_SIZE) {
+		fatal_error("Invalid fifo size %d", fifo_size);
+	}
+	if (fifo_size) {
+		context->fifo_read = 0;
+		context->fifo_write = fifo_size & (FIFO_SIZE - 1);
+		for (int i = 0; i < fifo_size; i++)
+		{
+			fifo_entry *entry = context->fifo + i;
+			entry->cycle = load_int32(buf);
+			entry->address = load_int32(buf);
+			entry->value = load_int16(buf);
+			entry->cd = load_int8(buf);
+			entry->partial = load_int8(buf);
+		}
+	} else {
+		context->fifo_read = -1;
+		context->fifo_write = 0;
+	}
+	uint16_t flags = load_int16(buf);
+	context->flags2 = flags >> 8;
+	context->flags = flags;
+	context->frame = load_int32(buf);
+	context->vcounter = load_int16(buf);
+	context->hslot = load_int8(buf);
+	context->hv_latch = load_int16(buf);
+	context->state = load_int8(buf);
+	context->hscroll_a = load_int16(buf);
+	context->hscroll_b = load_int16(buf);
+	context->vscroll_latch[0] = load_int16(buf);
+	context->vscroll_latch[1] = load_int16(buf);
+	context->col_1 = load_int16(buf);
+	context->col_2 = load_int16(buf);
+	context->test_port = load_int16(buf);
+	load_buffer8(buf, context->tmp_buf_a, SCROLL_BUFFER_SIZE);
+	load_buffer8(buf, context->tmp_buf_b, SCROLL_BUFFER_SIZE);
+	context->buf_a_off = load_int8(buf) & SCROLL_BUFFER_MASK;
+	context->buf_b_off = load_int8(buf) & SCROLL_BUFFER_MASK;
+	context->sprite_index = load_int8(buf);
+	context->sprite_draws = load_int8(buf);
+	context->slot_counter = load_int8(buf);
+	context->cur_slot = load_int8(buf);
+	for (int i = 0; i < MAX_DRAWS; i++)
+	{
+		sprite_draw *draw = context->sprite_draw_list + i;
+		draw->address = load_int16(buf);
+		draw->x_pos = load_int16(buf);
+		draw->pal_priority = load_int8(buf);
+		draw->h_flip = load_int8(buf);
+	}
+	for (int i = 0; i < MAX_SPRITES_LINE; i++)
+	{
+		sprite_info *info = context->sprite_info_list + i;
+		info->size = load_int8(buf);
+		info->index = load_int8(buf);
+		info->y = load_int16(buf);
+	}
+	load_buffer8(buf, context->linebuf, LINEBUF_SIZE);
+	
+	context->cycles = load_int32(buf);
+	context->pending_vint_start = load_int32(buf);
+	context->pending_hint_start = load_int32(buf);
+	update_video_params(context);
+}
--- a/vdp.h	Fri Jul 07 21:44:49 2017 -0700
+++ b/vdp.h	Sun Aug 06 00:06:36 2017 -0700
@@ -9,6 +9,7 @@
 #include <stdint.h>
 #include <stdio.h>
 #include "system.h"
+#include "serialize.h"
 
 #define VDP_REGS 24
 #define CRAM_SIZE 64
@@ -199,11 +200,9 @@
 	uint8_t     max_sprites_line;
 	uint8_t     fetch_tmp[2];
 	uint8_t     v_offset;
-	uint8_t     dma_cd;
 	uint8_t     hint_counter;
 	uint8_t     flags2;
 	uint8_t     double_res;
-	uint8_t     b32;
 	uint8_t     buf_a_off;
 	uint8_t     buf_b_off;
 	uint8_t     debug;
@@ -250,5 +249,7 @@
 void vdp_pbc_pause(vdp_context *context);
 void vdp_release_framebuffer(vdp_context *context);
 void vdp_reacquire_framebuffer(vdp_context *context);
+void vdp_serialize(vdp_context *context, serialize_buffer *buf);
+void vdp_deserialize(deserialize_buffer *buf, void *vcontext);
 
 #endif //VDP_H_
--- a/ym2612.c	Fri Jul 07 21:44:49 2017 -0700
+++ b/ym2612.c	Sun Aug 06 00:06:36 2017 -0700
@@ -1058,3 +1058,117 @@
 		   context->timer_control & BIT_TIMERB_ENABLE ? "yes" : "no");
 }
 
+void ym_serialize(ym2612_context *context, serialize_buffer *buf)
+{
+	save_buffer8(buf, context->part1_regs, YM_PART1_REGS);
+	save_buffer8(buf, context->part2_regs, YM_PART2_REGS);
+	for (int i = 0; i < NUM_OPERATORS; i++)
+	{
+		save_int32(buf, context->operators[i].phase_counter);
+		save_int16(buf, context->operators[i].envelope);
+		save_int16(buf, context->operators[i].output);
+		save_int8(buf, context->operators[i].env_phase);
+		save_int8(buf, context->operators[i].inverted);
+	}
+	for (int i = 0; i < NUM_CHANNELS; i++)
+	{
+		save_int16(buf, context->channels[i].output);
+		save_int16(buf, context->channels[i].op1_old);
+		//Due to the latching behavior, these need to be saved
+		//even though duplicate info is probably in the regs array
+		save_int8(buf, context->channels[i].block);
+		save_int8(buf, context->channels[i].fnum);
+	}
+	for (int i = 0; i < 3; i++)
+	{
+		//Due to the latching behavior, these need to be saved
+		//even though duplicate info is probably in the regs array
+		save_int8(buf, context->ch3_supp[i].block);
+		save_int8(buf, context->ch3_supp[i].fnum);
+	}
+	save_int16(buf, context->timer_a);
+	save_int8(buf, context->timer_b);
+	save_int8(buf, context->sub_timer_b);
+	save_int16(buf, context->env_counter);
+	save_int8(buf, context->current_op);
+	save_int8(buf, context->current_env_op);
+	save_int8(buf, context->lfo_counter);
+	save_int8(buf, context->csm_keyon);
+	save_int8(buf, context->status);
+	save_int8(buf, context->selected_reg);
+	save_int8(buf, context->selected_part);
+	save_int32(buf, context->current_cycle);
+	save_int32(buf, context->write_cycle);
+	save_int32(buf, context->busy_cycles);
+}
+
+void ym_deserialize(deserialize_buffer *buf, void *vcontext)
+{
+	ym2612_context *context = vcontext;
+	uint8_t temp_regs[YM_PART1_REGS];
+	load_buffer8(buf, temp_regs, YM_PART1_REGS);
+	context->selected_part = 0;
+	for (int i = 0; i < YM_PART1_REGS; i++)
+	{
+		uint8_t reg = YM_PART1_START + i;
+		if (reg != REG_FNUM_LOW && reg != REG_KEY_ONOFF && reg != REG_TIME_CTRL) {
+			context->selected_reg = reg;
+			ym_data_write(context, temp_regs[i]);
+		}
+	}
+	load_buffer8(buf, temp_regs, YM_PART2_REGS);
+	context->selected_part = 1;
+	for (int i = 0; i < YM_PART2_REGS; i++)
+	{
+		uint8_t reg = YM_PART2_START + i;
+		if (reg != REG_FNUM_LOW) {
+			context->selected_reg = reg;
+			ym_data_write(context, temp_regs[i]);
+		}
+	}
+	for (int i = 0; i < NUM_OPERATORS; i++)
+	{
+		context->operators[i].phase_counter = load_int32(buf);
+		context->operators[i].envelope = load_int16(buf);
+		context->operators[i].output = load_int16(buf);
+		context->operators[i].env_phase = load_int8(buf);
+		if (context->operators[i].env_phase > PHASE_RELEASE) {
+			context->operators[i].env_phase = PHASE_RELEASE;
+		}
+		context->operators[i].inverted = load_int8(buf) != 0;
+	}
+	for (int i = 0; i < NUM_CHANNELS; i++)
+	{
+		context->channels[i].output = load_int16(buf);
+		context->channels[i].op1_old = load_int16(buf);
+		context->channels[i].block = load_int8(buf);
+		context->channels[i].fnum = load_int8(buf);
+		context->channels[i].keycode = context->channels[i].block << 2 | fnum_to_keycode[context->channels[i].fnum >> 7];
+	}
+	for (int i = 0; i < 3; i++)
+	{
+		context->ch3_supp[i].block = load_int8(buf);
+		context->ch3_supp[i].fnum = load_int8(buf);
+		context->ch3_supp[i].keycode = context->ch3_supp[i].block << 2 | fnum_to_keycode[context->ch3_supp[i].fnum >> 7];
+	}
+	context->timer_a = load_int16(buf);
+	context->timer_b = load_int8(buf);
+	context->sub_timer_b = load_int8(buf);
+	context->env_counter = load_int16(buf);
+	context->current_op = load_int8(buf);
+	if (context->current_op >= NUM_OPERATORS) {
+		context->current_op = 0;
+	}
+	context->current_env_op = load_int8(buf);
+	if (context->current_env_op >= NUM_OPERATORS) {
+		context->current_env_op = 0;
+	}
+	context->lfo_counter = load_int8(buf);
+	context->csm_keyon = load_int8(buf);
+	context->status = load_int8(buf);
+	context->selected_reg = load_int8(buf);
+	context->selected_part = load_int8(buf);
+	context->current_cycle = load_int32(buf);
+	context->write_cycle = load_int32(buf);
+	context->busy_cycles = load_int32(buf);
+}
--- a/ym2612.h	Fri Jul 07 21:44:49 2017 -0700
+++ b/ym2612.h	Sun Aug 06 00:06:36 2017 -0700
@@ -8,6 +8,7 @@
 
 #include <stdint.h>
 #include <stdio.h>
+#include "serialize.h"
 
 #define NUM_PART_REGS (0xB7-0x30)
 #define NUM_CHANNELS 6
@@ -143,6 +144,8 @@
 uint8_t ym_save_gst(ym2612_context * context, FILE * gstfile);
 void ym_print_channel_info(ym2612_context *context, int channel);
 void ym_print_timer_info(ym2612_context *context);
+void ym_serialize(ym2612_context *context, serialize_buffer *buf);
+void ym_deserialize(deserialize_buffer *buf, void *vcontext);
 
 #endif //YM2612_H_
 
--- a/z80_to_x86.c	Fri Jul 07 21:44:49 2017 -0700
+++ b/z80_to_x86.c	Sun Aug 06 00:06:36 2017 -0700
@@ -3830,3 +3830,104 @@
 	}
 }
 
+void z80_serialize(z80_context *context, serialize_buffer *buf)
+{
+	for (int i = 0; i <= Z80_A; i++)
+	{
+		save_int8(buf, context->regs[i]);
+	}
+	uint8_t f = context->flags[ZF_S];
+	f <<= 1;
+	f |= context->flags[ZF_Z] ;
+	f <<= 2;
+	f |= context->flags[ZF_H];
+	f <<= 2;
+	f |= context->flags[ZF_PV];
+	f <<= 1;
+	f |= context->flags[ZF_N];
+	f <<= 1;
+	f |= context->flags[ZF_C];
+	f |= context->flags[ZF_XY] & 0x28;
+	save_int8(buf, f);
+	for (int i = 0; i <= Z80_A; i++)
+	{
+		save_int8(buf, context->alt_regs[i]);
+	}
+	f = context->alt_flags[ZF_S];
+	f <<= 1;
+	f |= context->alt_flags[ZF_Z] ;
+	f <<= 2;
+	f |= context->alt_flags[ZF_H];
+	f <<= 2;
+	f |= context->alt_flags[ZF_PV];
+	f <<= 1;
+	f |= context->alt_flags[ZF_N];
+	f <<= 1;
+	f |= context->alt_flags[ZF_C];
+	f |= context->flags[ZF_XY] & 0x28;
+	save_int8(buf, f);
+	save_int16(buf, context->pc);
+	save_int16(buf, context->sp);
+	save_int8(buf, context->im);
+	save_int8(buf, context->iff1);
+	save_int8(buf, context->iff2);
+	save_int8(buf, context->int_is_nmi);
+	save_int32(buf, context->current_cycle);
+	save_int32(buf, context->int_cycle);
+	save_int32(buf, context->int_enable_cycle);
+	save_int32(buf, context->int_pulse_start);
+	save_int32(buf, context->int_pulse_end);
+	save_int32(buf, context->nmi_start);
+}
+
+void z80_deserialize(deserialize_buffer *buf, void *vcontext)
+{
+	z80_context *context = vcontext;
+	for (int i = 0; i <= Z80_A; i++)
+	{
+		context->regs[i] = load_int8(buf);
+	}
+	uint8_t f = load_int8(buf);
+	context->flags[ZF_XY] = f & 0x28;
+	context->flags[ZF_C] = f & 1;
+	f >>= 1;
+	context->flags[ZF_N] = f & 1;
+	f >>= 1;
+	context->flags[ZF_PV] = f & 1;
+	f >>= 2;
+	context->flags[ZF_H] = f & 1;
+	f >>= 2;
+	context->flags[ZF_Z] = f & 1;
+	f >>= 1;
+	context->flags[ZF_S] = f;
+	for (int i = 0; i <= Z80_A; i++)
+	{
+		context->alt_regs[i] = load_int8(buf);
+	}
+	f = load_int8(buf);
+	context->alt_flags[ZF_XY] = f & 0x28;
+	context->alt_flags[ZF_C] = f & 1;
+	f >>= 1;
+	context->alt_flags[ZF_N] = f & 1;
+	f >>= 1;
+	context->alt_flags[ZF_PV] = f & 1;
+	f >>= 2;
+	context->alt_flags[ZF_H] = f & 1;
+	f >>= 2;
+	context->alt_flags[ZF_Z] = f & 1;
+	f >>= 1;
+	context->alt_flags[ZF_S] = f;
+	context->pc = load_int16(buf);
+	context->sp = load_int16(buf);
+	context->im = load_int8(buf);
+	context->iff1 = load_int8(buf);
+	context->iff2 = load_int8(buf);
+	context->int_is_nmi = load_int8(buf);
+	context->current_cycle = load_int32(buf);
+	context->int_cycle = load_int32(buf);
+	context->int_enable_cycle = load_int32(buf);
+	context->int_pulse_start = load_int32(buf);
+	context->int_pulse_end = load_int32(buf);
+	context->nmi_start = load_int32(buf);
+}
+
--- a/z80_to_x86.h	Fri Jul 07 21:44:49 2017 -0700
+++ b/z80_to_x86.h	Sun Aug 06 00:06:36 2017 -0700
@@ -7,6 +7,7 @@
 #define Z80_TO_X86_H_
 #include "z80inst.h"
 #include "backend.h"
+#include "serialize.h"
 
 #define ZNUM_MEM_AREAS 4
 #ifdef Z80_LOG_ADDRESS
@@ -108,6 +109,8 @@
 void z80_assert_nmi(z80_context *context, uint32_t cycle);
 uint8_t z80_get_busack(z80_context * context, uint32_t cycle);
 void z80_adjust_cycles(z80_context * context, uint32_t deduction);
+void z80_serialize(z80_context *context, serialize_buffer *buf);
+void z80_deserialize(deserialize_buffer *buf, void *vcontext);
 
 #endif //Z80_TO_X86_H_