changeset 1946:c3c62dbf1ceb

WIP netplay support
author Michael Pavone <pavone@retrodev.com>
date Wed, 29 Apr 2020 01:00:57 -0700
parents ba7231d2411c
children c36102d09351
files Makefile blastem.c genesis.c genesis.h psg.c render.h saves.h serialize.c system.c system.h vdp.c vdp.h ym2612.c
diffstat 13 files changed, 241 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Wed Apr 29 01:00:15 2020 -0700
+++ b/Makefile	Wed Apr 29 01:00:57 2020 -0700
@@ -195,7 +195,7 @@
 endif
 endif
 endif
-AUDIOOBJS=ym2612.o psg.o wave.o vgm.o render_audio.o
+AUDIOOBJS=ym2612.o psg.o wave.o vgm.o event_log.o render_audio.o
 CONFIGOBJS=config.o tern.o util.o paths.o 
 NUKLEAROBJS=$(FONT) nuklear_ui/blastem_nuklear.o nuklear_ui/sfnt.o
 RENDEROBJS=ppm.o controller_info.o
@@ -213,7 +213,7 @@
 
 MAINOBJS=blastem.o system.o genesis.o debug.o gdb_remote.o vdp.o $(RENDEROBJS) io.o romdb.o hash.o menu.o xband.o \
 	realtec.o i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \
-	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o zip.o bindings.o jcart.o
+	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o zip.o bindings.o jcart.o gen_player.o
 
 LIBOBJS=libblastem.o system.o genesis.o debug.o gdb_remote.o vdp.o io.o romdb.o hash.o xband.o realtec.o \
 	i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \
--- a/blastem.c	Wed Apr 29 01:00:15 2020 -0700
+++ b/blastem.c	Wed Apr 29 01:00:57 2020 -0700
@@ -30,6 +30,7 @@
 #include "bindings.h"
 #include "menu.h"
 #include "zip.h"
+#include "event_log.h"
 #ifndef DISABLE_NUKLEAR
 #include "nuklear_ui/blastem_nuklear.h"
 #endif
@@ -430,6 +431,23 @@
 	update_title(game_system->info.name);
 }
 
+char *parse_addr_port(char *arg)
+{
+	while (*arg && *arg != ':') {
+		++arg;
+	}
+	if (!*arg) {
+		return NULL;
+	}
+	char *end;
+	int port = strtol(arg + 1, &end, 10);
+	if (port && !*end) {
+		*arg = 0;
+		return arg + 1;
+	}
+	return NULL;
+}
+
 int main(int argc, char ** argv)
 {
 	set_exe_str(argv[0]);
@@ -441,10 +459,12 @@
 	system_type stype = SYSTEM_UNKNOWN, force_stype = SYSTEM_UNKNOWN;
 	char * romfname = NULL;
 	char * statefile = NULL;
+	event_reader reader = {0};
 	debugger_type dtype = DEBUGGER_NATIVE;
 	uint8_t start_in_debugger = 0;
 	uint8_t fullscreen = FULLSCREEN_DEFAULT, use_gl = 1;
 	uint8_t debug_target = 0;
+	char *port;
 	for (int i = 1; i < argc; i++) {
 		if (argv[i][0] == '-') {
 			switch(argv[i][1]) {
@@ -468,6 +488,18 @@
 				dtype = DEBUGGER_GDB;
 				start_in_debugger = 1;
 				break;
+			case 'e':
+				i++;
+				if (i >= argc) {
+					fatal_error("-e must be followed by a file name\n");
+				}
+				port = parse_addr_port(argv[i]);
+				if (port) {
+					event_log_tcp(argv[i], port);
+				} else {
+					event_log_file(argv[i]);
+				}
+				break;
 			case 'f':
 				fullscreen = !fullscreen;
 				break;
@@ -555,18 +587,24 @@
 					"	-v          Display version number and exit\n"
 					"	-l          Log 68K code addresses (useful for assemblers)\n"
 					"	-y          Log individual YM-2612 channels to WAVE files\n"
+					"   -e FILE     Write hardware event log to FILE\n"
 				);
 				return 0;
 			default:
 				fatal_error("Unrecognized switch %s\n", argv[i]);
 			}
 		} else if (!loaded) {
-			if (!(cart.size = load_rom(argv[i], &cart.buffer, stype == SYSTEM_UNKNOWN ? &stype : NULL))) {
-				fatal_error("Failed to open %s for reading\n", argv[i]);
+			char *port = parse_addr_port(argv[i]);
+			if (port) {
+				init_event_reader_tcp(&reader, argv[i], port);
+			} else {
+				if (!(cart.size = load_rom(argv[i], &cart.buffer, stype == SYSTEM_UNKNOWN ? &stype : NULL))) {
+					fatal_error("Failed to open %s for reading\n", argv[i]);
+				}
+				cart.dir = path_dirname(argv[i]);
+				cart.name = basename_no_extension(argv[i]);
+				cart.extension = path_extension(argv[i]);
 			}
-			cart.dir = path_dirname(argv[i]);
-			cart.name = basename_no_extension(argv[i]);
-			cart.extension = path_extension(argv[i]);
 			romfname = argv[i];
 			loaded = 1;
 		} else if (width < 0) {
@@ -645,13 +683,22 @@
 	}
 
 	if (loaded) {
-		if (stype == SYSTEM_UNKNOWN) {
-			stype = detect_system_type(&cart);
+		if (stype == SYSTEM_UNKNOWN || reader.socket) {
+			if (reader.socket) {
+				stype = reader_system_type(&reader);
+			} else {
+				stype = detect_system_type(&cart);
+			}
 		}
 		if (stype == SYSTEM_UNKNOWN) {
 			fatal_error("Failed to detect system type for %s\n", romfname);
 		}
-		current_system = alloc_config_system(stype, &cart, menu ? 0 : opts, force_region);
+		
+		if (reader.socket) {
+			current_system = alloc_config_player(stype, &reader);
+		} else {
+			current_system = alloc_config_system(stype, &cart, menu ? 0 : opts, force_region);
+		}
 		if (!current_system) {
 			fatal_error("Failed to configure emulated machine for %s\n", romfname);
 		}
--- a/genesis.c	Wed Apr 29 01:00:15 2020 -0700
+++ b/genesis.c	Wed Apr 29 01:00:57 2020 -0700
@@ -19,6 +19,7 @@
 #include "bindings.h"
 #include "jcart.h"
 #include "config.h"
+#include "event_log.h"
 #define MCLKS_NTSC 53693175
 #define MCLKS_PAL  53203395
 
@@ -49,15 +50,17 @@
 #define Z80_OPTS options
 #endif
 
-void genesis_serialize(genesis_context *gen, serialize_buffer *buf, uint32_t m68k_pc)
+void genesis_serialize(genesis_context *gen, serialize_buffer *buf, uint32_t m68k_pc, uint8_t all)
 {
-	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);
+	if (all) {
+		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);
@@ -71,35 +74,37 @@
 	psg_serialize(gen->psg, buf);
 	end_section(buf);
 	
-	start_section(buf, SECTION_GEN_BUS_ARBITER);
-	save_int8(buf, gen->z80->reset);
-	save_int8(buf, gen->z80->busreq);
-	save_int16(buf, gen->z80_bank_reg);
-	end_section(buf);
-	
-	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);
-	
-	cart_serialize(&gen->header, buf);
+	if (all) {
+		start_section(buf, SECTION_GEN_BUS_ARBITER);
+		save_int8(buf, gen->z80->reset);
+		save_int8(buf, gen->z80->busreq);
+		save_int16(buf, gen->z80_bank_reg);
+		end_section(buf);
+		
+		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);
+		
+		cart_serialize(&gen->header, buf);
+	}
 }
 
 static uint8_t *serialize(system_header *sys, size_t *size_out)
@@ -119,7 +124,7 @@
 		init_serialize(&state);
 		uint32_t address = read_word(4, (void **)gen->m68k->mem_pointers, &gen->m68k->options->gen, gen->m68k) << 16;
 		address |= read_word(6, (void **)gen->m68k->mem_pointers, &gen->m68k->options->gen, gen->m68k);
-		genesis_serialize(gen, &state, address);
+		genesis_serialize(gen, &state, address, 1);
 		if (size_out) {
 			*size_out = state.size;
 		}
@@ -411,6 +416,7 @@
 			if (gen->reset_cycle != CYCLE_NEVER) {
 				gen->reset_cycle -= deduction;
 			}
+			event_cycle_adjust(mclks, deduction);
 		}
 	}
 	gen->frame_end = vdp_cycles_to_frame_end(v_context);
@@ -449,16 +455,18 @@
 				}
 			}
 #endif
-			char *save_path = slot == SERIALIZE_SLOT ? NULL : get_slot_name(&gen->header, slot, use_native_states ? "state" : "gst");
-			if (use_native_states || slot == SERIALIZE_SLOT) {
+			char *save_path = slot >= SERIALIZE_SLOT ? NULL : get_slot_name(&gen->header, slot, use_native_states ? "state" : "gst");
+			if (use_native_states || slot >= SERIALIZE_SLOT) {
 				serialize_buffer state;
 				init_serialize(&state);
-				genesis_serialize(gen, &state, address);
+				genesis_serialize(gen, &state, address, slot != EVENTLOG_SLOT);
 				if (slot == SERIALIZE_SLOT) {
 					gen->serialize_tmp = state.data;
 					gen->serialize_size = state.size;
 					context->sync_cycle = context->current_cycle;
 					context->should_return = 1;
+				} else if (slot == EVENTLOG_SLOT) {
+					event_state(context->current_cycle, &state);
 				} else {
 					save_to_file(&state, save_path);
 					free(state.data);
@@ -1533,6 +1541,7 @@
 	gen->int_latency_prev2 = MCLKS_PER_68K * 16;
 	
 	render_set_video_standard((gen->version_reg & HZ50) ? VID_PAL : VID_NTSC);
+	event_system_start(SYSTEM_GENESIS, (gen->version_reg & HZ50) ? VID_PAL : VID_NTSC, rom->name);
 	
 	gen->ym = malloc(sizeof(ym2612_context));
 	char *fm = tern_find_ptr_default(model, "fm", "discrete 2612");
--- a/genesis.h	Wed Apr 29 01:00:15 2020 -0700
+++ b/genesis.h	Wed Apr 29 01:00:57 2020 -0700
@@ -71,7 +71,7 @@
 
 m68k_context * sync_components(m68k_context *context, uint32_t address);
 genesis_context *alloc_config_genesis(void *rom, uint32_t rom_size, void *lock_on, uint32_t lock_on_size, uint32_t system_opts, uint8_t force_region);
-void genesis_serialize(genesis_context *gen, serialize_buffer *buf, uint32_t m68k_pc);
+void genesis_serialize(genesis_context *gen, serialize_buffer *buf, uint32_t m68k_pc, uint8_t all);
 void genesis_deserialize(deserialize_buffer *buf, genesis_context *gen);
 
 #endif //GENESIS_H_
--- a/psg.c	Wed Apr 29 01:00:15 2020 -0700
+++ b/psg.c	Wed Apr 29 01:00:57 2020 -0700
@@ -5,6 +5,7 @@
 */
 #include "psg.h"
 #include "blastem.h"
+#include "event_log.h"
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
@@ -35,6 +36,7 @@
 	if (context->vgm) {
 		vgm_sn76489_write(context->vgm, context->cycles, value);
 	}
+	event_log(EVENT_PSG_REG, context->cycles, sizeof(value), &value);
 	if (value & 0x80) {
 		context->latch = value & 0x70;
 		uint8_t channel = value >> 5 & 0x3;
--- a/render.h	Wed Apr 29 01:00:15 2020 -0700
+++ b/render.h	Wed Apr 29 01:00:57 2020 -0700
@@ -77,8 +77,6 @@
 #define FRAMEBUFFER_UI 2
 #define FRAMEBUFFER_USER_START 3
 
-#include "vdp.h"
-
 typedef enum {
 	VID_NTSC,
 	VID_PAL,
--- a/saves.h	Wed Apr 29 01:00:15 2020 -0700
+++ b/saves.h	Wed Apr 29 01:00:57 2020 -0700
@@ -7,6 +7,7 @@
 
 #define QUICK_SAVE_SLOT 10
 #define SERIALIZE_SLOT 11
+#define EVENTLOG_SLOT 12
 
 typedef struct {
 	char   *desc;
--- a/serialize.c	Wed Apr 29 01:00:15 2020 -0700
+++ b/serialize.c	Wed Apr 29 01:00:57 2020 -0700
@@ -20,8 +20,13 @@
 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));
+		if (amount < buf->storage) {
+			buf->storage *= 2;
+		} else {
+			//doublign isn't enough, increase by the precise amount needed
+			buf->storage += amount - (buf->storage - buf->size);
+		}
+		buf->data = realloc(buf->data, buf->storage + sizeof(*buf));
 	}
 }
 
--- a/system.c	Wed Apr 29 01:00:15 2020 -0700
+++ b/system.c	Wed Apr 29 01:00:57 2020 -0700
@@ -1,6 +1,7 @@
 #include <string.h>
 #include "system.h"
 #include "genesis.h"
+#include "gen_player.h"
 #include "sms.h"
 
 uint8_t safe_cmp(char *str, long offset, uint8_t *buffer, long filesize)
@@ -21,6 +22,14 @@
 	) {
 		return SYSTEM_SMS;
 	}
+	if (safe_cmp("BLSTEL\x02", 0, media->buffer, media->size)) {
+		uint8_t *buffer = media->buffer;
+		if (media->size > 9 && buffer[7] == 0) {
+			return buffer[8] + 1;
+		}
+	}
+		
+	
 	//TODO: Detect Jaguar ROMs here
 	
 	//Header based detection failed, examine filename for clues
@@ -60,6 +69,8 @@
 	{
 	case SYSTEM_GENESIS:
 		return &(alloc_config_genesis(media->buffer, media->size, lock_on, lock_on_size, opts, force_region))->header;
+	case SYSTEM_GENESIS_PLAYER:
+		return &(alloc_config_gen_player(media->buffer, media->size))->header;
 #ifndef NO_Z80
 	case SYSTEM_SMS:
 		return &(alloc_configure_sms(media, opts, force_region))->header;
@@ -68,3 +79,13 @@
 		return NULL;
 	}
 }
+
+system_header *alloc_config_player(system_type stype, event_reader *reader)
+{
+	switch(stype)
+	{
+	case SYSTEM_GENESIS:
+		return &(alloc_config_gen_player_reader(reader))->header;
+	}
+	return NULL;
+}
--- a/system.h	Wed Apr 29 01:00:15 2020 -0700
+++ b/system.h	Wed Apr 29 01:00:57 2020 -0700
@@ -9,8 +9,10 @@
 typedef enum {
 	SYSTEM_UNKNOWN,
 	SYSTEM_GENESIS,
+	SYSTEM_GENESIS_PLAYER,
 	SYSTEM_SMS,
-	SYSTEM_JAGUAR
+	SYSTEM_SMS_PLAYER,
+	SYSTEM_JAGUAR,
 } system_type;
 
 typedef enum {
@@ -33,6 +35,7 @@
 
 #include "arena.h"
 #include "romdb.h"
+#include "event_log.h"
 
 struct system_header {
 	system_header           *next_context;
@@ -87,5 +90,6 @@
 
 system_type detect_system_type(system_media *media);
 system_header *alloc_config_system(system_type stype, system_media *media, uint32_t opts, uint8_t force_region);
+system_header *alloc_config_player(system_type stype, event_reader *reader);
 
 #endif //SYSTEM_H_
--- a/vdp.c	Wed Apr 29 01:00:15 2020 -0700
+++ b/vdp.c	Wed Apr 29 01:00:57 2020 -0700
@@ -9,6 +9,7 @@
 #include <string.h>
 #include "render.h"
 #include "util.h"
+#include "event_log.h"
 
 #define NTSC_INACTIVE_START 224
 #define PAL_INACTIVE_START 240
@@ -908,14 +909,17 @@
 		{
 		case VRAM_WRITE:
 			if ((context->regs[REG_MODE_2] & (BIT_128K_VRAM|BIT_MODE_5)) == (BIT_128K_VRAM|BIT_MODE_5)) {
+				event_vram_word(context->cycles, start->address, start->value);
 				vdp_check_update_sat(context, start->address, start->value);
 				write_vram_word(context, start->address, start->value);
 			} else {
 				uint8_t byte = start->partial == 1 ? start->value >> 8 : start->value;
-				vdp_check_update_sat_byte(context, start->address ^ 1, byte);
-				write_vram_byte(context, start->address ^ 1, byte);
+				uint32_t address = start->address ^ 1;
+				event_vram_byte(context->cycles, start->address, byte, context->regs[REG_AUTOINC]);
+				vdp_check_update_sat_byte(context, address, byte);
+				write_vram_byte(context, address, byte);
 				if (!start->partial) {
-					start->address = start->address ^ 1;
+					start->address = address;
 					start->partial = 1;
 					//skip auto-increment and removal of entry from fifo
 					return;
@@ -924,18 +928,20 @@
 			break;
 		case CRAM_WRITE: {
 			//printf("CRAM Write | %X to %X\n", start->value, (start->address/2) & (CRAM_SIZE-1));
+			uint16_t val;
 			if (start->partial == 3) {
-				uint16_t val;
 				if ((start->address & 1) && (context->regs[REG_MODE_2] & BIT_MODE_5)) {
 					val = (context->cram[start->address >> 1 & (CRAM_SIZE-1)] & 0xFF) | start->value << 8;
 				} else {
 					uint16_t address = (context->regs[REG_MODE_2] & BIT_MODE_5) ? start->address >> 1 & (CRAM_SIZE-1) : start->address & 0x1F;
 					val = (context->cram[address] & 0xFF00) | start->value;
 				}
-				write_cram(context, start->address, val);
 			} else {
-				write_cram(context, start->address, start->partial ? context->fifo[context->fifo_write].value : start->value);
+				val = start->partial ? context->fifo[context->fifo_write].value : start->value;
 			}
+			uint8_t buffer[3] = {start->address, val >> 8, val};
+			event_log(EVENT_CRAM, context->cycles, sizeof(buffer), buffer);
+			write_cram(context, start->address, val);
 			break;
 		}
 		case VSRAM_WRITE:
@@ -952,6 +958,8 @@
 				} else {
 					context->vsram[(start->address/2) & 63] = start->partial ? context->fifo[context->fifo_write].value : start->value;
 				}
+				uint8_t buffer[3] = {(start->address/2) & 63, context->vsram[(start->address/2) & 63] >> 8, context->vsram[(start->address/2) & 63]};
+				event_log(EVENT_VSRAM, context->cycles, sizeof(buffer), buffer);
 			}
 
 			break;
@@ -2110,6 +2118,8 @@
 			context->pushed_frame = 1;
 			context->fb = NULL;
 		}
+		//TODO: Check whether this happens before or after the cycle increment
+		event_flush(context->cycles);
 		vdp_update_per_frame_debug(context);
 		context->h40_lines = 0;
 		context->frame++;
@@ -3760,6 +3770,8 @@
 				/*if (reg == REG_MODE_4 && ((value ^ context->regs[reg]) & BIT_H40)) {
 					printf("Mode changed from H%d to H%d @ %d, frame: %d\n", context->regs[reg] & BIT_H40 ? 40 : 32, value & BIT_H40 ? 40 : 32, context->cycles, context->frame);
 				}*/
+				uint8_t buffer[2] = {reg, value};
+				event_log(EVENT_VDP_REG, context->cycles, sizeof(buffer), buffer);
 				context->regs[reg] = value;
 				if (reg == REG_MODE_4) {
 					context->double_res = (value & (BIT_INTERLACE | BIT_DOUBLE_RES)) == (BIT_INTERLACE | BIT_DOUBLE_RES);
@@ -4551,3 +4563,78 @@
 		}
 	}
 }
+
+void vdp_replay_event(vdp_context *context, uint8_t event, event_reader *reader)
+{
+	uint32_t address;
+	deserialize_buffer *buffer = &reader->buffer;
+	switch (event)
+	{
+	case EVENT_VRAM_BYTE:
+		address = load_int16(buffer);
+		break;
+	case EVENT_VRAM_BYTE_DELTA:
+		address = reader->last_byte_address + load_int8(buffer);
+		break;
+	case EVENT_VRAM_BYTE_ONE:
+		address = reader->last_byte_address + 1;
+		break;
+	case EVENT_VRAM_BYTE_AUTO:
+		address = reader->last_byte_address + context->regs[REG_AUTOINC];
+		break;
+	case EVENT_VRAM_WORD:
+		address = load_int8(buffer) << 16;
+		address |= load_int16(buffer);
+		break;
+	case EVENT_VRAM_WORD_DELTA:
+		address = reader->last_word_address + load_int8(buffer);
+		break;
+	case EVENT_VDP_REG:
+	case EVENT_CRAM:
+	case EVENT_VSRAM:
+		address = load_int8(buffer);
+		break;
+	}
+	
+	switch (event)
+	{
+	case EVENT_VDP_REG: {
+		uint8_t value = load_int8(buffer);
+		context->regs[address] = value;
+		if (address == REG_MODE_4) {
+			context->double_res = (value & (BIT_INTERLACE | BIT_DOUBLE_RES)) == (BIT_INTERLACE | BIT_DOUBLE_RES);
+			if (!context->double_res) {
+				context->flags2 &= ~FLAG2_EVEN_FIELD;
+			}
+		}
+		if (address == REG_MODE_1 || address == REG_MODE_2 || address == REG_MODE_4) {
+			update_video_params(context);
+		}
+		break;
+	}
+	case EVENT_VRAM_BYTE:
+	case EVENT_VRAM_BYTE_DELTA:
+	case EVENT_VRAM_BYTE_ONE:
+	case EVENT_VRAM_BYTE_AUTO: {
+		uint8_t byte = load_int8(buffer);
+		reader->last_byte_address = address;
+		vdp_check_update_sat_byte(context, address ^ 1, byte);
+		write_vram_byte(context, address ^ 1, byte);
+		break;
+	}
+	case EVENT_VRAM_WORD:
+	case EVENT_VRAM_WORD_DELTA: {
+		uint16_t value = load_int16(buffer);
+		reader->last_word_address = address;
+		vdp_check_update_sat(context, address, value);
+		write_vram_word(context, address, value);
+		break;
+	}
+	case EVENT_CRAM:
+		write_cram(context, address, load_int16(buffer));
+		break;
+	case EVENT_VSRAM:
+		context->vsram[address] = load_int16(buffer);
+		break;
+	}
+}
--- a/vdp.h	Wed Apr 29 01:00:15 2020 -0700
+++ b/vdp.h	Wed Apr 29 01:00:57 2020 -0700
@@ -285,5 +285,6 @@
 void vdp_inc_debug_mode(vdp_context *context);
 //to be implemented by the host system
 uint16_t read_dma_value(uint32_t address);
+void vdp_replay_event(vdp_context *context, uint8_t event, event_reader *reader);
 
 #endif //VDP_H_
--- a/ym2612.c	Wed Apr 29 01:00:15 2020 -0700
+++ b/ym2612.c	Wed Apr 29 01:00:57 2020 -0700
@@ -11,6 +11,7 @@
 #include "render.h"
 #include "wave.h"
 #include "blastem.h"
+#include "event_log.h"
 
 //#define DO_DEBUG_PRINT
 #ifdef DO_DEBUG_PRINT
@@ -825,6 +826,8 @@
 		}
 		context->part1_regs[context->selected_reg - YM_PART1_START] = value;
 	}
+	uint8_t buffer[3] = {context->selected_part, context->selected_reg, value};
+	event_log(EVENT_YM_REG, context->current_cycle, sizeof(buffer), buffer);
 	dfprintf(debug_file, "write of %X to reg %X in part %d\n", value, context->selected_reg, context->selected_part+1);
 	if (context->selected_reg < 0x30) {
 		//Shared regs