From 85dbc05ded1437572ce9a459b692c5957b473dd8 Mon Sep 17 00:00:00 2001 From: Themaister Date: Sun, 13 Feb 2011 16:40:24 +0100 Subject: [PATCH] Some start on netplay. :) --- Makefile | 2 +- general.h | 5 ++ netplay.c | 260 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ netplay.h | 59 +++++++++++++ ssnes.c | 78 ++++++++++++---- 5 files changed, 385 insertions(+), 19 deletions(-) create mode 100644 netplay.c create mode 100644 netplay.h diff --git a/Makefile b/Makefile index 8f0024f3ab..23f3222310 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ include config.mk TARGET = ssnes tools/ssnes-joyconfig -OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o autosave.o +OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o autosave.o netplay.o JOYCONFIG_OBJ = tools/ssnes-joyconfig.o conf/config_file.o HEADERS = $(wildcard */*.h) $(wildcard *.h) diff --git a/general.h b/general.h index cafcdd6680..c2c5d2d785 100644 --- a/general.h +++ b/general.h @@ -27,6 +27,7 @@ #include "rewind.h" #include "movie.h" #include "autosave.h" +#include "netplay.h" #ifdef HAVE_CONFIG_H #include "config.h" @@ -178,6 +179,10 @@ struct global autosave_t *autosave[2]; + netplay_t *netplay; + char netplay_server[256]; + bool netplay_enable; + #ifdef HAVE_FFMPEG ffemu_t *rec; char record_path[256]; diff --git a/netplay.c b/netplay.c new file mode 100644 index 0000000000..194ba6f2e7 --- /dev/null +++ b/netplay.c @@ -0,0 +1,260 @@ +/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010-2011 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + +#include "netplay.h" +#include "general.h" +#include "dynamic.h" +#include +#include + +#ifdef _WIN32 +#define _WIN32_WINNT 0x0501 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#define close(x) closesocket(x) +#define CONST_CAST (const char*) +#else +#include +#include +#include +#include +#include +#include +#define CONST_CAST +#endif + +struct netplay +{ + struct snes_callbacks cbs; + int fd; + unsigned port; + bool has_connection; + unsigned frames; + + uint16_t input_state; +}; + +void input_poll_net(void) +{ + netplay_callbacks(g_extern.netplay)->poll_cb(); + netplay_poll(g_extern.netplay); +} + +void video_frame_net(const uint16_t *data, unsigned width, unsigned height) +{ + netplay_callbacks(g_extern.netplay)->frame_cb(data, width, height); +} + +void audio_sample_net(uint16_t left, uint16_t right) +{ + netplay_callbacks(g_extern.netplay)->sample_cb(left, right); +} + +int16_t input_state_net(bool port, unsigned device, unsigned index, unsigned id) +{ + if (netplay_is_port(g_extern.netplay, port, index)) + return netplay_input_state(g_extern.netplay, port, device, index, id); + else + return netplay_callbacks(g_extern.netplay)->state_cb(port, device, index, id); +} + +static bool init_socket(netplay_t *handle, const char *server, uint16_t port) +{ +#ifdef _WIN32 + WSADATA wsaData; + int retval; + + if ((retval = WSAStartup(MAKEWORD(2,2), &wsaData)) != 0) + { + WSACleanup(); + return false; + } +#endif + + struct addrinfo hints, *res = NULL; + memset(&hints, 0, sizeof(hints)); +#ifdef _WIN32 // Lolol, no AF_UNSPEC, wtf. + hints.ai_family = AF_INET; +#else + hints.ai_family = AF_UNSPEC; +#endif + hints.ai_socktype = SOCK_STREAM; + if (!server) + hints.ai_flags = AI_PASSIVE; + + char port_buf[16]; + snprintf(port_buf, sizeof(port_buf), "%hu", (unsigned short)port); + if (getaddrinfo(server, port_buf, &hints, &res) < 0) + return false; + + handle->fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (handle->fd < 0) + { + freeaddrinfo(res); + return false; + } + + if (server) + { + if (connect(handle->fd, res->ai_addr, res->ai_addrlen) < 0) + { + close(handle->fd); + freeaddrinfo(res); + return false; + } + } + else + { + if (bind(handle->fd, res->ai_addr, res->ai_addrlen) < 0 || listen(handle->fd, 1) < 0) + { + close(handle->fd); + freeaddrinfo(res); + return false; + } + int new_fd = accept(handle->fd, NULL, NULL); + if (new_fd < 0) + { + close(handle->fd); + freeaddrinfo(res); + return false; + } + close(handle->fd); + handle->fd = new_fd; + } + + freeaddrinfo(res); + + const int nodelay = 1; + setsockopt(handle->fd, SOL_SOCKET, TCP_NODELAY, &nodelay, sizeof(int)); + + return true; +} + +static bool send_info(netplay_t *handle) +{ + uint32_t header[2] = { htonl(g_extern.cart_crc), htonl(psnes_serialize_size()) }; + if (send(handle->fd, header, sizeof(header), 0) != sizeof(header)) + return false; + return true; +} + +static bool get_info(netplay_t *handle) +{ + uint32_t header[2]; + if (recv(handle->fd, header, sizeof(header), 0) != sizeof(header)) + return false; + if (g_extern.cart_crc != ntohl(header[0])) + return false; + if (psnes_serialize_size() != ntohl(header[1])) + return false; + return true; +} + +netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const struct snes_callbacks *cb) +{ + netplay_t *handle = calloc(1, sizeof(*handle)); + if (!handle) + return NULL; + + handle->cbs = *cb; + handle->port = server ? 0 : 1; + handle->frames = frames; + + if (!init_socket(handle, server, port)) + { + free(handle); + return NULL; + } + + if (server) + { + if (!send_info(handle)) + { + close(handle->fd); + free(handle); + return NULL; + } + } + else + { + if (!get_info(handle)) + { + close(handle->fd); + free(handle); + return NULL; + } + } + + handle->has_connection = true; + return handle; +} + +bool netplay_is_port(netplay_t *handle, bool port, unsigned index) +{ + if (!handle->has_connection) + return false; + unsigned port_num = port ? 1 : 0; + if (handle->port == port_num) + return true; + else + return false; +} + +bool netplay_poll(netplay_t *handle) +{ + uint16_t state = 0; + snes_input_state_t cb = handle->cbs.state_cb; + for (int i = 0; i <= 11; i++) + { + int16_t tmp = cb(!handle->port, SNES_DEVICE_JOYPAD, 0, i); + state |= tmp ? 1 << i : 0; + } + + state = htons(state); + if (send(handle->fd, &state, sizeof(state), 0) != sizeof(state)) + { + handle->has_connection = false; + return false; + } + + if (recv(handle->fd, &handle->input_state, sizeof(handle->input_state), 0) != sizeof(handle->input_state)) + { + handle->has_connection = false; + return false; + } + + handle->input_state = ntohs(handle->input_state); + return true; +} + +int16_t netplay_input_state(netplay_t *handle, bool port, unsigned device, unsigned index, unsigned id) +{ + return ((1 << id) & handle->input_state) ? 1 : 0; +} + +void netplay_free(netplay_t *handle) +{ + close(handle->fd); + free(handle); +} + +const struct snes_callbacks* netplay_callbacks(netplay_t *handle) +{ + return &handle->cbs; +} diff --git a/netplay.h b/netplay.h new file mode 100644 index 0000000000..7760b1a11f --- /dev/null +++ b/netplay.h @@ -0,0 +1,59 @@ +/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010-2011 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + + +#ifndef __SSNES_NETPLAY_H +#define __SSNES_NETPLAY_H + +#include +#include +#include + +void input_poll_net(void); +int16_t input_state_net(bool port, unsigned device, unsigned index, unsigned id); +void video_frame_net(const uint16_t *data, unsigned width, unsigned height); +void audio_sample_net(uint16_t left, uint16_t right); + +typedef struct netplay netplay_t; + +struct snes_callbacks +{ + snes_video_refresh_t frame_cb; + snes_audio_sample_t sample_cb; + snes_input_poll_t poll_cb; + snes_input_state_t state_cb; +}; + +// Creates a new netplay handle. A NULL host means we're hosting (player 1). :) +netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const struct snes_callbacks *cb); +void netplay_free(netplay_t *handle); + +// Checks if input port/index is controlled by netplay or not. +bool netplay_is_port(netplay_t *handle, bool port, unsigned index); + +bool netplay_poll(netplay_t *handle); +int16_t netplay_input_state(netplay_t *handle, bool port, unsigned device, unsigned index, unsigned id); + +// If we're fast-forward replaying to resync, check if we should actually show frame. +// bool netplay_should_skip(netplay_t *handle); +const struct snes_callbacks* netplay_callbacks(netplay_t *handle); + + + + + +#endif diff --git a/ssnes.c b/ssnes.c index b17b0b93d3..383823aac2 100644 --- a/ssnes.c +++ b/ssnes.c @@ -31,6 +31,7 @@ #include "record/ffemu.h" #include "rewind.h" #include "movie.h" +#include "netplay.h" #include #ifdef HAVE_SRC #include @@ -342,6 +343,8 @@ static void print_help(void) puts("\t-J/--justifiers: Daisy chain two virtual Konami Justifiers into port 2 of the SNES."); puts("\t-4/--multitap: Connect a multitap to port 2 of the SNES."); puts("\t-P/--bsvplay: Playback a BSV movie file."); + puts("\t-H/--host: Host netplay as player 1."); + puts("\t-C/--connect: Connect to netplay as player 2."); #ifdef HAVE_FFMPEG puts("\t-r/--record: Path to record video file. Settings for video/audio codecs are found in config file."); @@ -391,6 +394,8 @@ static void parse_input(int argc, char *argv[]) { "sufamiA", 1, NULL, 'Y' }, { "sufamiB", 1, NULL, 'Z' }, { "bsvplay", 1, NULL, 'P' }, + { "host", 0, NULL, 'H' }, + { "connect", 1, NULL, 'C' }, { NULL, 0, NULL, 0 } }; @@ -408,7 +413,7 @@ static void parse_input(int argc, char *argv[]) #define CONFIG_FILE_ARG #endif - char optstring[] = "hs:vS:m:p4jJg:b:B:Y:Z:P:" FFMPEG_RECORD_ARG CONFIG_FILE_ARG; + char optstring[] = "hs:vS:m:p4jJg:b:B:Y:Z:P:HC:" FFMPEG_RECORD_ARG CONFIG_FILE_ARG; for(;;) { int c = getopt_long(argc, argv, optstring, opts, &option_index); @@ -507,6 +512,15 @@ static void parse_input(int argc, char *argv[]) g_extern.bsv_movie_playback = true; break; + case 'H': + g_extern.netplay_enable = true; + break; + + case 'C': + g_extern.netplay_enable = true; + strncpy(g_extern.netplay_server, optarg, sizeof(g_extern.netplay_server) - 1); + break; + case '?': print_help(); exit(1); @@ -741,6 +755,28 @@ static void deinit_movie(void) bsv_movie_free(g_extern.bsv_movie); } +static void init_netplay(void) +{ + if (g_extern.netplay_enable) + { + struct snes_callbacks cbs = { + .frame_cb = video_frame, + .sample_cb = audio_sample, + .poll_cb = input_poll, + .state_cb = input_state + }; + g_extern.netplay = netplay_new(strlen(g_extern.netplay_server) > 0 ? g_extern.netplay_server : NULL, 55435, 1, &cbs); + if (!g_extern.netplay) + SSNES_WARN("Failed to init netplay...\n"); + } +} + +static void deinit_netplay(void) +{ + if (g_extern.netplay) + netplay_free(g_extern.netplay); +} + static void init_autosave(void) { int ram_types[2] = {-1, -1}; @@ -1110,23 +1146,27 @@ static void check_pause(void) static void do_state_checks(void) { - check_pause(); - if (g_extern.is_paused) - return; - - set_fast_forward_button(driver.input->key_pressed(driver.input_data, SSNES_FAST_FORWARD_KEY)); - - if (!g_extern.bsv_movie) + if (!g_extern.netplay) { - check_stateslots(); - check_savestates(); - check_rewind(); + check_pause(); + if (g_extern.is_paused) + return; + + set_fast_forward_button(driver.input->key_pressed(driver.input_data, SSNES_FAST_FORWARD_KEY)); + + if (!g_extern.bsv_movie) + { + check_stateslots(); + check_savestates(); + check_rewind(); + } + + if (!g_extern.bsv_movie_playback) + check_movie_record(); } + check_fullscreen(); check_input_rate(); - - if (!g_extern.bsv_movie_playback) - check_movie_record(); } @@ -1148,11 +1188,12 @@ int main(int argc, char *argv[]) goto error; init_drivers(); + init_netplay(); - psnes_set_video_refresh(video_frame); - psnes_set_audio_sample(audio_sample); - psnes_set_input_poll(input_poll); - psnes_set_input_state(input_state); + psnes_set_video_refresh(g_extern.netplay ? video_frame_net : video_frame); + psnes_set_audio_sample(g_extern.netplay ? audio_sample_net : audio_sample); + psnes_set_input_poll(g_extern.netplay ? input_poll_net : input_poll); + psnes_set_input_state(g_extern.netplay ? input_state_net : input_state); init_msg_queue(); init_controllers(); @@ -1203,6 +1244,7 @@ int main(int argc, char *argv[]) } } + deinit_netplay(); deinit_autosave(); #ifdef HAVE_FFMPEG