summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Pavone <pavone@retrodev.com>2020-04-29 23:42:16 -0700
committerMichael Pavone <pavone@retrodev.com>2020-04-29 23:42:16 -0700
commite2915e2ac57ac58d745eba210288cfe4061f0082 (patch)
treea1c9ad00704f47a31760146e65eccf0cbfc62829
parent2071d8e545b9aca6af08852f721e9a7b7a617398 (diff)
Add missing netplay files and add in support for sending gamepad commands back to host
-rw-r--r--event_log.c445
-rw-r--r--event_log.h51
-rw-r--r--gen_player.c153
-rw-r--r--gen_player.h22
4 files changed, 671 insertions, 0 deletions
diff --git a/event_log.c b/event_log.c
new file mode 100644
index 0000000..2db6f68
--- /dev/null
+++ b/event_log.c
@@ -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);
+}
diff --git a/event_log.h b/event_log.h
new file mode 100644
index 0000000..e994b8f
--- /dev/null
+++ b/event_log.h
@@ -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_
diff --git a/gen_player.c b/gen_player.c
new file mode 100644
index 0000000..c282517
--- /dev/null
+++ b/gen_player.c
@@ -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;
+}
+
diff --git a/gen_player.h b/gen_player.h
new file mode 100644
index 0000000..0b451da
--- /dev/null
+++ b/gen_player.h
@@ -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_