changeset 1947:c36102d09351

Add missing netplay files and add in support for sending gamepad commands back to host
author Michael Pavone <pavone@retrodev.com>
date Wed, 29 Apr 2020 23:42:16 -0700
parents c3c62dbf1ceb
children d01527615c7c
files event_log.c event_log.h gen_player.c gen_player.h
diffstat 4 files changed, 671 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/event_log.c	Wed Apr 29 23:42:16 2020 -0700
@@ -0,0 +1,445 @@
+#ifdef _WIN32
+#define WINVER 0x501
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/tcp.h>
+#endif
+
+#include <errno.h>
+#include "event_log.h"
+#include "util.h"
+#include "blastem.h"
+#include "saves.h"
+
+enum {
+	CMD_GAMEPAD_DOWN,
+	CMD_GAMEPAD_UP,
+};
+
+static uint8_t active, fully_active;
+static FILE *event_file;
+static serialize_buffer buffer;
+
+static const char el_ident[] = "BLSTEL\x02\x00";
+static uint32_t last;
+void event_log_file(char *fname)
+{
+	event_file = fopen(fname, "wb");
+	if (!event_file) {
+		warning("Failed to open event file %s for writing\n", fname);
+		return;
+	}
+	fwrite(el_ident, 1, sizeof(el_ident) - 1, event_file);
+	init_serialize(&buffer);
+	active = fully_active = 1;
+	last = 0;
+}
+
+static int listen_sock, remotes[7];
+static int num_remotes;
+void event_log_tcp(char *address, char *port)
+{
+	struct addrinfo request, *result;
+	memset(&request, 0, sizeof(request));
+	request.ai_family = AF_INET;
+	request.ai_socktype = SOCK_STREAM;
+	request.ai_flags = AI_PASSIVE;
+	getaddrinfo(address, port, &request, &result);
+	
+	listen_sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
+	if (listen_sock < 0) {
+		warning("Failed to open event log listen socket on %s:%s\n", address, port);
+		goto cleanup_address;
+	}
+	int non_block = 1;
+	setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &non_block, sizeof(non_block));
+	if (bind(listen_sock, result->ai_addr, result->ai_addrlen) < 0) {
+		warning("Failed to bind event log listen socket on %s:%s\n", address, port);
+		close(listen_sock);
+		goto cleanup_address;
+	}
+	if (listen(listen_sock, 3) < 0) {
+		warning("Failed to listen for event log remotes on %s:%s\n", address, port);
+		close(listen_sock);
+		goto cleanup_address;
+	}
+	fcntl(listen_sock, F_SETFL, O_NONBLOCK);
+	active = 1;
+cleanup_address:
+	freeaddrinfo(result);
+}
+
+static uint8_t *system_start;
+static size_t system_start_size;
+void event_system_start(system_type stype, vid_std video_std, char *name)
+{
+	if (!active) {
+		return;
+	}
+	save_int8(&buffer, stype);
+	save_int8(&buffer, video_std);
+	size_t name_len = strlen(name);
+	if (name_len > 255) {
+		name_len = 255;
+	}
+	save_int8(&buffer, name_len);
+	save_buffer8(&buffer, name, strlen(name));
+	if (!fully_active) {
+		system_start = malloc(buffer.size);
+		system_start_size = buffer.size;
+		memcpy(system_start, buffer.data, buffer.size);
+		buffer.size = 0;
+	}
+}
+
+//header formats
+//Single byte: 4 bit type, 4 bit delta (16-31)
+//Three Byte: 8 bit type, 16-bit delta
+//Four byte: 8-bit type, 24-bit signed delta
+#define FORMAT_3BYTE 0xE0
+#define FORMAT_4BYTE 0xF0
+static void event_header(uint8_t type, uint32_t cycle)
+{
+	uint32_t delta = cycle - last;
+	if (delta > 65535) {
+		save_int8(&buffer, FORMAT_4BYTE | type);
+		save_int8(&buffer, delta >> 16);
+		save_int16(&buffer, delta);
+	} else if (delta >= 16 && delta < 32) {
+		save_int8(&buffer, type << 4 | (delta - 16));
+	} else {
+		save_int8(&buffer, FORMAT_3BYTE | type);
+		save_int16(&buffer, delta);
+	}
+}
+
+void event_cycle_adjust(uint32_t cycle, uint32_t deduction)
+{
+	if (!fully_active) {
+		return;
+	}
+	event_header(EVENT_ADJUST, cycle);
+	last = cycle - deduction;
+	save_int32(&buffer, deduction);
+}
+
+static size_t remote_send_progress[7];
+static uint8_t remote_needs_state[7];
+static void flush_socket(void)
+{
+	int remote = accept(listen_sock, NULL, NULL);
+	if (remote != -1) {
+		if (num_remotes == 7) {
+			close(remote);
+		} else {
+			printf("remote %d connected\n", num_remotes);
+			remotes[num_remotes] = remote;
+			remote_needs_state[num_remotes++] = 1;
+			current_system->save_state = EVENTLOG_SLOT + 1;
+		}
+	}
+	size_t min_progress = 0;
+	for (int i = 0; i < num_remotes; i++) {
+		errno = 0;
+		int sent = 1;
+		if (remote_needs_state[i]) {
+			remote_send_progress[i] = buffer.size;
+		} else {
+			uint8_t buffer[1500];
+			int bytes = recv(remotes[i], buffer, sizeof(buffer), 0);
+			for (int j = 0; j < bytes; j++)
+			{
+				uint8_t cmd = buffer[j];
+				switch(cmd)
+				{
+				case CMD_GAMEPAD_DOWN:
+				case CMD_GAMEPAD_UP: {
+					++j;
+					if (j < bytes) {
+						uint8_t button = buffer[j];
+						uint8_t pad = (button >> 5) + i + 1;
+						button &= 0x1F;
+						if (cmd == CMD_GAMEPAD_DOWN) {
+							current_system->gamepad_down(current_system, pad, button);
+						} else {
+							current_system->gamepad_up(current_system, pad, button);
+						}
+					} else {
+						warning("Received incomplete command %X\n", cmd);
+					}
+					break;
+				}
+				default:
+					warning("Unrecognized remote command %X\n", cmd);
+					j = bytes;
+				}
+			}
+		}
+		while (sent && buffer.size - remote_send_progress[i])
+		{
+			sent = send(remotes[i], buffer.data + remote_send_progress[i], buffer.size - remote_send_progress[i], 0);
+			if (sent >= 0) {
+				remote_send_progress[i] += sent;
+			} else if (errno != EAGAIN && errno != EWOULDBLOCK) {
+				close(remotes[i]);
+				remotes[i] = remotes[num_remotes-1];
+				remote_send_progress[i] = remote_send_progress[num_remotes-1];
+				remote_needs_state[i] = remote_needs_state[num_remotes-1];
+				num_remotes--;
+				i--;
+				break;
+			}
+			if (remote_send_progress[i] > min_progress) {
+				min_progress = remote_send_progress[i];
+			}
+		}
+	}
+	if (min_progress == buffer.size) {
+		buffer.size = 0;
+		memset(remote_send_progress, 0, sizeof(remote_send_progress));
+	}
+}
+
+void event_log(uint8_t type, uint32_t cycle, uint8_t size, uint8_t *payload)
+{
+	if (!fully_active) {
+		return;
+	}
+	event_header(type, cycle);
+	last = cycle;
+	save_buffer8(&buffer, payload, size);
+	if (listen_sock && buffer.size > 1280) {
+		flush_socket();
+	}
+}
+
+static uint32_t last_word_address;
+void event_vram_word(uint32_t cycle, uint32_t address, uint16_t value)
+{
+	uint32_t delta = address - last_word_address;
+	if (delta < 256) {
+		uint8_t buffer[3] = {delta, value >> 8, value};
+		event_log(EVENT_VRAM_WORD_DELTA, cycle, sizeof(buffer), buffer);
+	} else {
+		uint8_t buffer[5] = {address >> 16, address >> 8, address, value >> 8, value};
+		event_log(EVENT_VRAM_WORD, cycle, sizeof(buffer), buffer);
+	}
+	last_word_address = address;
+}
+
+static uint32_t last_byte_address;
+void event_vram_byte(uint32_t cycle, uint16_t address, uint8_t byte, uint8_t auto_inc)
+{
+	uint32_t delta = address - last_byte_address;
+	if (delta == 1) {
+		event_log(EVENT_VRAM_BYTE_ONE, cycle, sizeof(byte), &byte);
+	} else if (delta == auto_inc) {
+		event_log(EVENT_VRAM_BYTE_AUTO, cycle, sizeof(byte), &byte);
+	} else if (delta < 256) {
+		uint8_t buffer[2] = {delta, byte};
+		event_log(EVENT_VRAM_BYTE_DELTA, cycle, sizeof(buffer), buffer);
+	} else {
+		uint8_t buffer[3] = {address >> 8, address, byte};
+		event_log(EVENT_VRAM_BYTE, cycle, sizeof(buffer), buffer);
+	}
+	last_byte_address = address;
+}
+
+static size_t send_all(int sock, uint8_t *data, size_t size, int flags)
+{
+	size_t total = 0, sent = 1;
+	while(sent > 0 && total < size)
+	{
+		sent = send(sock, data + total, size - total, flags);
+		if (sent > 0) {
+			total += sent;
+		}
+	}
+	return total;
+}
+
+void event_state(uint32_t cycle, serialize_buffer *state)
+{
+	if (!fully_active) {
+		last = cycle;
+	}
+	uint8_t header[] = {
+		EVENT_STATE << 4, last >> 24, last >> 16, last >> 8, last,
+		last_word_address >> 16, last_word_address >> 8, last_word_address,
+		last_byte_address >> 8, last_byte_address,
+		state->size >> 16, state->size >> 8, state->size
+	};
+	for (int i = 0; i < num_remotes; i++)
+	{
+		if (remote_needs_state[i]) {
+			if(
+				send_all(remotes[i], system_start, system_start_size, 0) == system_start_size
+				&& send_all(remotes[i], header, sizeof(header), 0) == sizeof(header)
+				&& send_all(remotes[i], state->data, state->size, 0) == state->size
+			) {
+				remote_send_progress[i] = buffer.size;
+				remote_needs_state[i] = 0;
+				fcntl(remotes[i], F_SETFL, O_NONBLOCK);
+				int flag = 1;
+				setsockopt(remotes[i], IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
+				fully_active = 1;
+			} else {
+				close(remotes[i]);
+				remotes[i] = remotes[num_remotes-1];
+				remote_send_progress[i] = remote_send_progress[num_remotes-1];
+				remote_needs_state[i] = remote_needs_state[num_remotes-1];
+				num_remotes--;
+				i--;
+			}
+		}
+	}
+}
+
+void event_flush(uint32_t cycle)
+{
+	if (!active) {
+		return;
+	}
+	if (fully_active) {
+		event_log(EVENT_FLUSH, cycle, 0, NULL);
+	}
+	if (event_file) {
+		fwrite(buffer.data, 1, buffer.size, event_file);
+		fflush(event_file);
+		buffer.size = 0;
+	} else if (listen_sock) {
+		flush_socket();
+	}
+}
+
+void init_event_reader(event_reader *reader, uint8_t *data, size_t size)
+{
+	reader->socket = 0;
+	reader->last_cycle = 0;
+	init_deserialize(&reader->buffer, data, size);
+}
+
+void init_event_reader_tcp(event_reader *reader, char *address, char *port)
+{
+	struct addrinfo request, *result;
+	memset(&request, 0, sizeof(request));
+	request.ai_family = AF_INET;
+	request.ai_socktype = SOCK_STREAM;
+	request.ai_flags = AI_PASSIVE;
+	getaddrinfo(address, port, &request, &result);
+	
+	reader->socket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
+	if (reader->socket < 0) {
+		fatal_error("Failed to create socket for event log connection to %s:%s\n", address, port);
+	}
+	if (connect(reader->socket, result->ai_addr, result->ai_addrlen) < 0) {
+		fatal_error("Failed to connect to %s:%s for event log stream\n", address, port);
+	}
+	
+	reader->storage = 512 * 1024;
+	reader->last_cycle = 0;
+	init_deserialize(&reader->buffer, malloc(reader->storage), reader->storage);
+	reader->buffer.size = 0;
+	while(reader->buffer.size < 3 || reader->buffer.size < 3 + reader->buffer.data[2])
+	{
+		int bytes = recv(reader->socket, reader->buffer.data + reader->buffer.size, reader->storage - reader->buffer.size, 0);
+		if (bytes < 0) {
+			fatal_error("Failed to receive system init from %s:%s\n", address, port);
+		}
+		reader->buffer.size += bytes;
+	}
+	fcntl(reader->socket, F_SETFL, O_NONBLOCK);
+	int flag = 1;
+	setsockopt(reader->socket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
+}
+
+uint8_t reader_next_event(event_reader *reader, uint32_t *cycle_out)
+{
+	if (reader->socket) {
+		uint8_t blocking = 0;
+		if (reader->buffer.size - reader->buffer.cur_pos < 9) {
+			//set back to block mode
+			fcntl(reader->socket, F_SETFL, 0);
+			blocking = 1;
+		}
+		if (reader->storage - (reader->buffer.size - reader->buffer.cur_pos) < 128 * 1024) {
+			reader->storage *= 2;
+			uint8_t *new_buf = malloc(reader->storage);
+			memcpy(new_buf, reader->buffer.data + reader->buffer.cur_pos, reader->buffer.size - reader->buffer.cur_pos);
+			free(reader->buffer.data);
+			reader->buffer.data = new_buf;
+			reader->buffer.size -= reader->buffer.cur_pos;
+			reader->buffer.cur_pos = 0;
+		} else if (reader->buffer.cur_pos >= reader->buffer.size/2 && reader->buffer.size >= reader->storage/2) {
+			memmove(reader->buffer.data, reader->buffer.data + reader->buffer.cur_pos, reader->buffer.size - reader->buffer.cur_pos);
+			reader->buffer.size -= reader->buffer.cur_pos;
+			reader->buffer.cur_pos = 0;
+		}
+		int bytes = 128;
+		while (bytes > 127 && reader->buffer.size < reader->storage)
+		{
+			errno = 0;
+			bytes = recv(reader->socket, reader->buffer.data + reader->buffer.size, reader->storage - reader->buffer.size, 0);
+			if (bytes >= 0) {
+				reader->buffer.size += bytes;
+				if (blocking && reader->buffer.size - reader->buffer.cur_pos >= 9) {
+					fcntl(reader->socket, F_SETFL, O_NONBLOCK);
+				}
+			} else if (errno != EAGAIN && errno != EWOULDBLOCK) {
+				puts("Connection closed");
+				exit(0);
+			}
+		}
+	}
+	uint8_t header = load_int8(&reader->buffer);
+	uint8_t ret;
+	uint32_t delta;
+	if ((header & 0xF0) < FORMAT_3BYTE) {
+		delta = header & 0xF + 16;
+		ret = header >> 4;
+	} else if ((header & 0xF0) == FORMAT_3BYTE) {
+		delta = load_int16(&reader->buffer);
+		ret = header & 0xF;
+	} else {
+		delta = load_int8(&reader->buffer) << 16;
+		//sign extend 24-bit delta to 32-bit
+		if (delta & 0x800000) {
+			delta |= 0xFF000000;
+		}
+		delta |= load_int16(&reader->buffer);
+		ret = header & 0xF;
+	}
+	*cycle_out = reader->last_cycle + delta;
+	reader->last_cycle = *cycle_out;
+	if (ret == EVENT_ADJUST) {
+		size_t old_pos = reader->buffer.cur_pos;
+		uint32_t adjust = load_int32(&reader->buffer);
+		reader->buffer.cur_pos = old_pos;
+		reader->last_cycle -= adjust;
+	} else if (ret == EVENT_STATE) {
+		reader->last_cycle = load_int32(&reader->buffer);
+		reader->last_word_address = load_int8(&reader->buffer) << 16;
+		reader->last_word_address |= load_int16(&reader->buffer);
+		reader->last_byte_address = load_int16(&reader->buffer);
+	}
+	return ret;
+}
+
+uint8_t reader_system_type(event_reader *reader)
+{
+	return load_int8(&reader->buffer);
+}
+
+void reader_send_gamepad_event(event_reader *reader, uint8_t pad, uint8_t button, uint8_t down)
+{
+	uint8_t buffer[] = {down ? CMD_GAMEPAD_DOWN : CMD_GAMEPAD_UP, pad << 5 | button};
+	//TODO: Deal with the fact that we're not in blocking mode so this may not actually send all
+	//if the buffer is full
+	send_all(reader->socket, buffer, sizeof(buffer), 0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/event_log.h	Wed Apr 29 23:42:16 2020 -0700
@@ -0,0 +1,51 @@
+#ifndef EVENT_LOG_H_
+#define EVENT_LOG_H_
+
+enum {
+	EVENT_FLUSH = 0,
+	EVENT_ADJUST = 1,
+	EVENT_PSG_REG = 2,
+	EVENT_YM_REG = 3,
+	EVENT_VDP_REG = 4,
+	EVENT_VRAM_BYTE = 5,
+	EVENT_VRAM_BYTE_DELTA = 6,
+	EVENT_VRAM_BYTE_ONE = 7,
+	EVENT_VRAM_BYTE_AUTO = 8,
+	EVENT_VRAM_WORD = 9,
+	EVENT_VRAM_WORD_DELTA = 10,
+	EVENT_CRAM = 11,
+	EVENT_VSRAM = 12,
+	EVENT_STATE = 13
+	//14 and 15 are reserved for header types
+};
+
+#include "serialize.h"
+typedef struct {
+	size_t storage;
+	int socket;
+	uint32_t last_cycle;
+	uint32_t last_word_address;
+	uint32_t last_byte_address;
+	deserialize_buffer buffer;
+} event_reader;
+
+#include "system.h"
+#include "render.h"
+
+void event_log_file(char *fname);
+void event_log_tcp(char *address, char *port);
+void event_system_start(system_type stype, vid_std video_std, char *name);
+void event_cycle_adjust(uint32_t cycle, uint32_t deduction);
+void event_log(uint8_t type, uint32_t cycle, uint8_t size, uint8_t *payload);
+void event_vram_word(uint32_t cycle, uint32_t address, uint16_t value);
+void event_vram_byte(uint32_t cycle, uint16_t address, uint8_t byte, uint8_t auto_inc);
+void event_state(uint32_t cycle, serialize_buffer *state);
+void event_flush(uint32_t cycle);
+
+void init_event_reader(event_reader *reader, uint8_t *data, size_t size);
+void init_event_reader_tcp(event_reader *reader, char *address, char *port);
+uint8_t reader_next_event(event_reader *reader, uint32_t *cycle_out);
+uint8_t reader_system_type(event_reader *reader);
+void reader_send_gamepad_event(event_reader *reader, uint8_t pad, uint8_t button, uint8_t down);
+
+#endif //EVENT_LOG_H_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gen_player.c	Wed Apr 29 23:42:16 2020 -0700
@@ -0,0 +1,153 @@
+#include "gen_player.h"
+#include "event_log.h"
+#include "render.h"
+
+#ifdef IS_LIB
+#define MAX_SOUND_CYCLES (MCLKS_PER_YM*NUM_OPERATORS*6*4)
+#else
+#define MAX_SOUND_CYCLES 100000	
+#endif
+
+static void sync_sound(gen_player *gen, uint32_t target)
+{
+	//printf("YM | Cycle: %d, bpos: %d, PSG | Cycle: %d, bpos: %d\n", gen->ym->current_cycle, gen->ym->buffer_pos, gen->psg->cycles, gen->psg->buffer_pos * 2);
+	while (target > gen->psg->cycles && target - gen->psg->cycles > MAX_SOUND_CYCLES) {
+		uint32_t cur_target = gen->psg->cycles + MAX_SOUND_CYCLES;
+		//printf("Running PSG to cycle %d\n", cur_target);
+		psg_run(gen->psg, cur_target);
+		//printf("Running YM-2612 to cycle %d\n", cur_target);
+		ym_run(gen->ym, cur_target);
+	}
+	psg_run(gen->psg, target);
+	ym_run(gen->ym, target);
+
+	//printf("Target: %d, YM bufferpos: %d, PSG bufferpos: %d\n", target, gen->ym->buffer_pos, gen->psg->buffer_pos * 2);
+}
+
+void start_context(system_header *sys, char *statefile)
+{
+	gen_player *player = (gen_player *)sys;
+	while(player->reader.socket || player->reader.buffer.cur_pos < player->reader.buffer.size)
+	{
+		uint32_t cycle;
+		uint8_t event = reader_next_event(&player->reader, &cycle);
+		switch (event)
+		{
+		case EVENT_FLUSH:
+			sync_sound(player, cycle);
+			vdp_run_context(player->vdp, cycle);
+			break;
+		case EVENT_ADJUST: {
+			sync_sound(player, cycle);
+			vdp_run_context(player->vdp, cycle);
+			uint32_t deduction = load_int32(&player->reader.buffer);
+			ym_adjust_cycles(player->ym, deduction);
+			vdp_adjust_cycles(player->vdp, deduction);
+			player->psg->cycles -= deduction;
+			break;
+		case EVENT_PSG_REG:
+			sync_sound(player, cycle);
+			psg_write(player->psg, load_int8(&player->reader.buffer));
+			break;
+		case EVENT_YM_REG: {
+			sync_sound(player, cycle);
+			uint8_t part = load_int8(&player->reader.buffer);
+			uint8_t reg = load_int8(&player->reader.buffer);
+			uint8_t value = load_int8(&player->reader.buffer);
+			if (part) {
+				ym_address_write_part2(player->ym, reg);
+			} else {
+				ym_address_write_part1(player->ym, reg);
+			}
+			ym_data_write(player->ym, value);
+			break;
+		case EVENT_STATE: {
+			uint32_t size = load_int8(&player->reader.buffer) << 16;
+			size |= load_int16(&player->reader.buffer);
+			if (player->reader.buffer.size - player->reader.buffer.cur_pos < size) {
+				puts("State has not been fully loaded!");
+				exit(1);
+			}
+			deserialize_buffer buffer;
+			init_deserialize(&buffer, player->reader.buffer.data + player->reader.buffer.cur_pos, size);
+			register_section_handler(&buffer, (section_handler){.fun = vdp_deserialize, .data = player->vdp}, SECTION_VDP);
+			register_section_handler(&buffer, (section_handler){.fun = ym_deserialize, .data = player->ym}, SECTION_YM2612);
+			register_section_handler(&buffer, (section_handler){.fun = psg_deserialize, .data = player->psg}, SECTION_PSG);
+			while (buffer.cur_pos < buffer.size)
+			{
+				load_section(&buffer);
+			}
+			player->reader.buffer.cur_pos += size;
+			free(buffer.handlers);
+			break;
+		}
+		default:
+			vdp_run_context(player->vdp, cycle);
+			vdp_replay_event(player->vdp, event, &player->reader);
+		}
+		}
+			
+		}
+	}
+	
+}
+
+static void gamepad_down(system_header *system, uint8_t gamepad_num, uint8_t button)
+{
+	gen_player *player = (gen_player *)system;
+	reader_send_gamepad_event(&player->reader, gamepad_num, button, 1);
+}
+
+static void gamepad_up(system_header *system, uint8_t gamepad_num, uint8_t button)
+{
+	gen_player *player = (gen_player *)system;
+	reader_send_gamepad_event(&player->reader, gamepad_num, button, 0);
+}
+
+#define MCLKS_NTSC 53693175
+#define MCLKS_PAL  53203395
+#define MCLKS_PER_YM  7
+#define MCLKS_PER_Z80 15
+#define MCLKS_PER_PSG (MCLKS_PER_Z80*16)
+
+static void config_common(gen_player *player)
+{
+	uint8_t vid_std = load_int8(&player->reader.buffer);
+	uint8_t name_len = load_int8(&player->reader.buffer);
+	player->header.info.name = calloc(1, name_len + 1);
+	load_buffer8(&player->reader.buffer, player->header.info.name, name_len);
+	
+	player->vdp = init_vdp_context(vid_std == VID_PAL, 0);
+	render_set_video_standard(vid_std);
+	uint32_t master_clock = vid_std == VID_NTSC ? MCLKS_NTSC : MCLKS_PAL;
+	
+	player->ym = malloc(sizeof(ym2612_context));
+	ym_init(player->ym, master_clock, MCLKS_PER_YM, 0);
+	
+	player->psg = malloc(sizeof(psg_context));
+	psg_init(player->psg, master_clock, MCLKS_PER_PSG);
+	
+	player->header.start_context = start_context;
+	player->header.gamepad_down = gamepad_down;
+	player->header.gamepad_up = gamepad_up;
+	player->header.type = SYSTEM_GENESIS_PLAYER;
+	player->header.info.save_type = SAVE_NONE;
+}
+
+gen_player *alloc_config_gen_player(void *stream, uint32_t rom_size)
+{
+	uint8_t *data = stream;
+	gen_player *player = calloc(1, sizeof(gen_player));
+	init_event_reader(&player->reader, data + 9, rom_size - 9);
+	config_common(player);
+	return player;
+}
+
+gen_player *alloc_config_gen_player_reader(event_reader *reader)
+{
+	gen_player *player = calloc(1, sizeof(gen_player));
+	player->reader = *reader;
+	config_common(player);
+	return player;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gen_player.h	Wed Apr 29 23:42:16 2020 -0700
@@ -0,0 +1,22 @@
+#ifndef GEN_PLAYER_H_
+#define GEN_PLAYER_H_
+
+#include "system.h"
+#include "vdp.h"
+#include "psg.h"
+#include "ym2612.h"
+#include "event_log.h"
+
+typedef struct {
+	system_header   header;
+	
+	vdp_context     *vdp;
+	ym2612_context  *ym;
+	psg_context     *psg;
+	event_reader    reader;
+} gen_player;
+
+gen_player *alloc_config_gen_player(void *stream, uint32_t rom_size);
+gen_player *alloc_config_gen_player_reader(event_reader *reader);
+
+#endif //GEN_PLAYER_H_