mirror of
https://github.com/CTCaer/RetroArch.git
synced 2024-12-04 09:03:24 +00:00
Spectator mode.
This commit is contained in:
parent
36a2c96cb7
commit
4beec3870f
@ -168,6 +168,15 @@ Set FRAMES to 0 to have perfect sync. 0 frames is only suitable for LAN. Default
|
||||
\fB--port PORT\fR
|
||||
Network port used for netplay. This defaults to 55435. This option affects both TCP and UDP.
|
||||
|
||||
.TP
|
||||
\fB--spectate\fR
|
||||
If netplay is used, it will go into a spectator mode.
|
||||
Spectator mode allows one host to live stream game playback to multiple clients.
|
||||
Essentially, clients receive a live streamed BSV movie file.
|
||||
Clients can connect and disconnect at any time.
|
||||
Clients thus cannot interact as player 2.
|
||||
For spectating mode to work, both host and clients will need to use this flag.
|
||||
|
||||
.TP
|
||||
\fB--ups PATCH, -U PATCH\fR
|
||||
Attempts to apply an UPS patch to the current ROM image. No files are altered.
|
||||
|
@ -309,6 +309,7 @@ struct global
|
||||
char netplay_server[PATH_MAX];
|
||||
bool netplay_enable;
|
||||
bool netplay_is_client;
|
||||
bool netplay_is_spectate;
|
||||
unsigned netplay_sync_frames;
|
||||
uint16_t netplay_port;
|
||||
#endif
|
||||
|
55
movie.c
55
movie.c
@ -18,6 +18,7 @@
|
||||
#include "movie.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "general.h"
|
||||
#include "dynamic.h"
|
||||
|
||||
@ -327,3 +328,57 @@ void bsv_movie_frame_rewind(bsv_movie_t *handle)
|
||||
fseek(handle->file, handle->min_file_pos, SEEK_SET);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t *bsv_header_generate(size_t *size)
|
||||
{
|
||||
uint32_t bsv_header[4] = {0};
|
||||
unsigned serialize_size = psnes_serialize_size();
|
||||
size_t header_size = sizeof(bsv_header) + serialize_size;
|
||||
*size = header_size;
|
||||
|
||||
uint8_t *header = (uint8_t*)malloc(header_size);
|
||||
if (!header)
|
||||
return NULL;
|
||||
|
||||
bsv_header[MAGIC_INDEX] = swap_if_little32(BSV_MAGIC);
|
||||
bsv_header[CRC_INDEX] = swap_if_big32(g_extern.cart_crc);
|
||||
bsv_header[STATE_SIZE_INDEX] = swap_if_big32(serialize_size);
|
||||
|
||||
if (serialize_size && !psnes_serialize(header + sizeof(bsv_header), serialize_size))
|
||||
{
|
||||
free(header);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(header, bsv_header, sizeof(bsv_header));
|
||||
return header;
|
||||
}
|
||||
|
||||
bool bsv_parse_header(const uint32_t *header)
|
||||
{
|
||||
uint32_t in_bsv = swap_if_little32(header[MAGIC_INDEX]);
|
||||
if (in_bsv != BSV_MAGIC)
|
||||
{
|
||||
SSNES_ERR("BSV magic mismatch, got 0x%x, expected 0x%x!\n",
|
||||
in_bsv, BSV_MAGIC);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t in_crc = swap_if_big32(header[CRC_INDEX]);
|
||||
if (in_crc != g_extern.cart_crc)
|
||||
{
|
||||
SSNES_ERR("CRC32 mismatch, got 0x%x, expected 0x%x!\n", in_crc, g_extern.cart_crc);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t in_state_size = swap_if_big32(header[STATE_SIZE_INDEX]);
|
||||
if (in_state_size != psnes_serialize_size())
|
||||
{
|
||||
SSNES_ERR("Serialization size mismatch, got 0x%x, expected 0x%x!\n",
|
||||
in_state_size, psnes_serialize_size());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
4
movie.h
4
movie.h
@ -19,6 +19,7 @@
|
||||
#define __SSNES_MOVIE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "boolean.h"
|
||||
|
||||
typedef struct bsv_movie bsv_movie_t;
|
||||
@ -29,6 +30,9 @@ enum ssnes_movie_type
|
||||
SSNES_MOVIE_RECORD
|
||||
};
|
||||
|
||||
uint8_t *bsv_header_generate(size_t *size);
|
||||
bool bsv_parse_header(const uint32_t *header);
|
||||
|
||||
bsv_movie_t *bsv_movie_init(const char *path, enum ssnes_movie_type type);
|
||||
|
||||
// Playback
|
||||
|
351
netplay.c
351
netplay.c
@ -42,6 +42,18 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// Checks if input port/index is controlled by netplay or not.
|
||||
static bool netplay_is_alive(netplay_t *handle);
|
||||
|
||||
static bool netplay_poll(netplay_t *handle);
|
||||
static 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.
|
||||
static bool netplay_should_skip(netplay_t *handle);
|
||||
static bool netplay_can_poll(netplay_t *handle);
|
||||
static const struct snes_callbacks* netplay_callbacks(netplay_t *handle);
|
||||
static void netplay_set_spectate_input(netplay_t *handle, int16_t input);
|
||||
|
||||
#ifdef _WIN32
|
||||
// Woohoo, Winsock has headers from the STONE AGE! :D
|
||||
#define close(x) closesocket(x)
|
||||
@ -69,6 +81,7 @@ struct delta_frame
|
||||
};
|
||||
|
||||
#define UDP_FRAME_PACKETS 16
|
||||
#define MAX_SPECTATORS 16
|
||||
|
||||
struct netplay
|
||||
{
|
||||
@ -100,6 +113,14 @@ struct netplay
|
||||
bool has_client_addr;
|
||||
|
||||
unsigned timeout_cnt;
|
||||
|
||||
// Spectating.
|
||||
bool spectate;
|
||||
bool spectate_client;
|
||||
int spectate_fds[MAX_SPECTATORS];
|
||||
uint16_t *spectate_input;
|
||||
size_t spectate_input_ptr;
|
||||
size_t spectate_input_size;
|
||||
};
|
||||
|
||||
static void warn_hangup(void)
|
||||
@ -135,7 +156,7 @@ int16_t input_state_net(bool port, unsigned device, unsigned index, unsigned id)
|
||||
return netplay_callbacks(g_extern.netplay)->state_cb(port, device, index, id);
|
||||
}
|
||||
|
||||
static bool init_tcp_socket(netplay_t *handle, const char *server, uint16_t port)
|
||||
static bool init_tcp_socket(netplay_t *handle, const char *server, uint16_t port, bool spectate)
|
||||
{
|
||||
struct addrinfo hints, *res = NULL;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
@ -176,12 +197,27 @@ static bool init_tcp_socket(netplay_t *handle, const char *server, uint16_t port
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (handle->spectate)
|
||||
{
|
||||
int yes = 1;
|
||||
setsockopt(handle->fd, SOL_SOCKET, SO_REUSEADDR, CONST_CAST &yes, sizeof(int));
|
||||
|
||||
if (bind(handle->fd, res->ai_addr, res->ai_addrlen) < 0 ||
|
||||
listen(handle->fd, MAX_SPECTATORS) < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to bind socket.\n");
|
||||
close(handle->fd);
|
||||
freeaddrinfo(res);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int yes = 1;
|
||||
setsockopt(handle->fd, SOL_SOCKET, SO_REUSEADDR, CONST_CAST &yes, sizeof(int));
|
||||
|
||||
if (bind(handle->fd, res->ai_addr, res->ai_addrlen) < 0 || listen(handle->fd, 1) < 0)
|
||||
if (bind(handle->fd, res->ai_addr, res->ai_addrlen) < 0 ||
|
||||
listen(handle->fd, 1) < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to bind socket.\n");
|
||||
close(handle->fd);
|
||||
@ -265,10 +301,13 @@ static bool init_socket(netplay_t *handle, const char *server, uint16_t port)
|
||||
signal(SIGPIPE, SIG_IGN); // Do not like SIGPIPE killing our app :(
|
||||
#endif
|
||||
|
||||
if (!init_tcp_socket(handle, server, port))
|
||||
return false;
|
||||
if (!init_udp_socket(handle, server, port))
|
||||
if (!init_tcp_socket(handle, server, port, handle->spectate))
|
||||
return false;
|
||||
if (!handle->spectate)
|
||||
{
|
||||
if (!init_udp_socket(handle, server, port))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -346,7 +385,7 @@ static bool get_info(netplay_t *handle)
|
||||
// Send SRAM data to our Player 2 :)
|
||||
const uint8_t *sram = psnes_get_memory_data(SNES_MEMORY_CARTRIDGE_RAM);
|
||||
unsigned sram_size = psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM);
|
||||
while (sram_size > 0)
|
||||
while (sram_size)
|
||||
{
|
||||
ssize_t ret = send(handle->fd, CONST_CAST sram, sram_size, 0);
|
||||
if (ret <= 0)
|
||||
@ -361,6 +400,50 @@ static bool get_info(netplay_t *handle)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool get_info_spectate(netplay_t *handle)
|
||||
{
|
||||
uint32_t header[4];
|
||||
if (recv(handle->fd, NONCONST_CAST header, sizeof(header), 0) != (ssize_t)sizeof(header))
|
||||
{
|
||||
SSNES_ERR("Cannot get header from host!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned save_state_size = psnes_serialize_size();
|
||||
if (!bsv_parse_header(header))
|
||||
{
|
||||
SSNES_ERR("Received invalid BSV header from host!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t *buf = (uint8_t*)malloc(save_state_size);
|
||||
if (!buf)
|
||||
return false;
|
||||
|
||||
size_t size = save_state_size;
|
||||
uint8_t *tmp_buf = buf;
|
||||
while (size)
|
||||
{
|
||||
ssize_t ret = recv(handle->fd, NONCONST_CAST tmp_buf, size, 0);
|
||||
if (ret <= 0)
|
||||
{
|
||||
SSNES_ERR("Failed to receive save state from host!\n");
|
||||
free(tmp_buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
size -= ret;
|
||||
tmp_buf += ret;
|
||||
}
|
||||
|
||||
bool ret = true;
|
||||
if (save_state_size)
|
||||
ret = psnes_unserialize(buf, save_state_size);
|
||||
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void init_buffers(netplay_t *handle)
|
||||
{
|
||||
handle->buffer = (struct delta_frame*)calloc(handle->buffer_size, sizeof(*handle->buffer));
|
||||
@ -372,8 +455,10 @@ static void init_buffers(netplay_t *handle)
|
||||
}
|
||||
}
|
||||
|
||||
netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const struct snes_callbacks *cb)
|
||||
netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const struct snes_callbacks *cb, bool spectate)
|
||||
{
|
||||
(void)spectate;
|
||||
|
||||
if (frames > UDP_FRAME_PACKETS)
|
||||
frames = UDP_FRAME_PACKETS;
|
||||
|
||||
@ -381,8 +466,12 @@ netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const
|
||||
if (!handle)
|
||||
return NULL;
|
||||
|
||||
handle->fd = -1;
|
||||
handle->udp_fd = -1;
|
||||
handle->cbs = *cb;
|
||||
handle->port = server ? 0 : 1;
|
||||
handle->spectate = spectate;
|
||||
handle->spectate_client = server != NULL;
|
||||
|
||||
if (!init_socket(handle, server, port))
|
||||
{
|
||||
@ -390,35 +479,50 @@ netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (server)
|
||||
if (spectate)
|
||||
{
|
||||
if (!send_info(handle))
|
||||
if (server)
|
||||
{
|
||||
close(handle->fd);
|
||||
free(handle);
|
||||
return NULL;
|
||||
if (!get_info_spectate(handle))
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < MAX_SPECTATORS; i++)
|
||||
handle->spectate_fds[i] = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!get_info(handle))
|
||||
if (server)
|
||||
{
|
||||
close(handle->fd);
|
||||
free(handle);
|
||||
return NULL;
|
||||
if (!send_info(handle))
|
||||
goto error;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!get_info(handle))
|
||||
goto error;
|
||||
}
|
||||
|
||||
handle->buffer_size = frames + 1;
|
||||
|
||||
init_buffers(handle);
|
||||
handle->has_connection = true;
|
||||
}
|
||||
|
||||
handle->buffer_size = frames + 1;
|
||||
|
||||
init_buffers(handle);
|
||||
handle->has_connection = true;
|
||||
|
||||
return handle;
|
||||
|
||||
error:
|
||||
if (handle->fd >= 0)
|
||||
close(handle->fd);
|
||||
if (handle->udp_fd >= 0)
|
||||
close(handle->udp_fd);
|
||||
|
||||
free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
bool netplay_is_alive(netplay_t *handle)
|
||||
static bool netplay_is_alive(netplay_t *handle)
|
||||
{
|
||||
return handle->has_connection;
|
||||
}
|
||||
@ -571,7 +675,7 @@ static bool receive_data(netplay_t *handle, uint32_t *buffer, size_t size)
|
||||
}
|
||||
|
||||
// Poll network to see if we have anything new. If our network buffer is full, we simply have to block for new input data.
|
||||
bool netplay_poll(netplay_t *handle)
|
||||
static bool netplay_poll(netplay_t *handle)
|
||||
{
|
||||
if (!handle->has_connection)
|
||||
return false;
|
||||
@ -665,28 +769,42 @@ int16_t netplay_input_state(netplay_t *handle, bool port, unsigned device, unsig
|
||||
void netplay_free(netplay_t *handle)
|
||||
{
|
||||
close(handle->fd);
|
||||
close(handle->udp_fd);
|
||||
|
||||
for (unsigned i = 0; i < handle->buffer_size; i++)
|
||||
free(handle->buffer[i].state);
|
||||
|
||||
free(handle->buffer);
|
||||
if (handle->spectate)
|
||||
{
|
||||
for (unsigned i = 0; i < MAX_SPECTATORS; i++)
|
||||
if (handle->spectate_fds[i] >= 0)
|
||||
close(handle->spectate_fds[i]);
|
||||
|
||||
free(handle->spectate_input);
|
||||
}
|
||||
else
|
||||
{
|
||||
close(handle->udp_fd);
|
||||
|
||||
for (unsigned i = 0; i < handle->buffer_size; i++)
|
||||
free(handle->buffer[i].state);
|
||||
|
||||
free(handle->buffer);
|
||||
}
|
||||
|
||||
if (handle->addr)
|
||||
freeaddrinfo(handle->addr);
|
||||
|
||||
free(handle);
|
||||
}
|
||||
|
||||
const struct snes_callbacks* netplay_callbacks(netplay_t *handle)
|
||||
static const struct snes_callbacks* netplay_callbacks(netplay_t *handle)
|
||||
{
|
||||
return &handle->cbs;
|
||||
}
|
||||
|
||||
bool netplay_should_skip(netplay_t *handle)
|
||||
static bool netplay_should_skip(netplay_t *handle)
|
||||
{
|
||||
return handle->is_replay && handle->has_connection;
|
||||
}
|
||||
|
||||
void netplay_pre_frame(netplay_t *handle)
|
||||
static void netplay_pre_frame_net(netplay_t *handle)
|
||||
{
|
||||
psnes_serialize(handle->buffer[handle->self_ptr].state, handle->state_size);
|
||||
handle->can_poll = true;
|
||||
@ -694,8 +812,133 @@ void netplay_pre_frame(netplay_t *handle)
|
||||
input_poll_net();
|
||||
}
|
||||
|
||||
// Here we check if we have new input and replay from recorded input.
|
||||
void netplay_post_frame(netplay_t *handle)
|
||||
static inline uint16_t swap_if_big16(uint16_t input)
|
||||
{
|
||||
if (is_little_endian())
|
||||
return input;
|
||||
else
|
||||
return (input << 8) | (input >> 8);
|
||||
}
|
||||
|
||||
static void netplay_set_spectate_input(netplay_t *handle, int16_t input)
|
||||
{
|
||||
if (handle->spectate_input_ptr >= handle->spectate_input_size)
|
||||
{
|
||||
handle->spectate_input_size++;
|
||||
handle->spectate_input_size *= 2;
|
||||
handle->spectate_input = (uint16_t*)realloc(handle->spectate_input,
|
||||
handle->spectate_input_size * sizeof(uint16_t));
|
||||
}
|
||||
|
||||
handle->spectate_input[handle->spectate_input_ptr++] = swap_if_big16(input);
|
||||
}
|
||||
|
||||
int16_t input_state_spectate(bool port, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
int16_t res = netplay_callbacks(g_extern.netplay)->state_cb(port, device, index, id);
|
||||
netplay_set_spectate_input(g_extern.netplay, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int16_t netplay_get_spectate_input(netplay_t *handle, bool port, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
int16_t inp;
|
||||
if (recv(handle->fd, NONCONST_CAST &inp, sizeof(inp), 0) == (ssize_t)sizeof(inp))
|
||||
return swap_if_big16(inp);
|
||||
else
|
||||
{
|
||||
SSNES_ERR("Connection with host was cut!\n");
|
||||
msg_queue_clear(g_extern.msg_queue);
|
||||
msg_queue_push(g_extern.msg_queue, "Connection with host was cut!", 1, 180);
|
||||
|
||||
psnes_set_input_state(netplay_callbacks(g_extern.netplay)->state_cb);
|
||||
return netplay_callbacks(g_extern.netplay)->state_cb(port, device, index, id);
|
||||
}
|
||||
}
|
||||
|
||||
int16_t input_state_spectate_client(bool port, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
return netplay_get_spectate_input(g_extern.netplay, port, device, index, id);
|
||||
}
|
||||
|
||||
static void netplay_pre_frame_spectate(netplay_t *handle)
|
||||
{
|
||||
if (handle->spectate_client)
|
||||
return;
|
||||
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(handle->fd, &fds);
|
||||
|
||||
struct timeval tmp_tv = {0};
|
||||
if (select(handle->fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0)
|
||||
return;
|
||||
|
||||
if (!FD_ISSET(handle->fd, &fds))
|
||||
return;
|
||||
|
||||
int new_fd = accept(handle->fd, NULL, NULL);
|
||||
if (new_fd < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to accept incoming spectator!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int index = -1;
|
||||
for (unsigned i = 0; i < MAX_SPECTATORS; i++)
|
||||
{
|
||||
if (handle->spectate_fds[i] == -1)
|
||||
{
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// No vacant client streams :(
|
||||
if (index == -1)
|
||||
{
|
||||
close(new_fd);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t header_size;
|
||||
uint8_t *header = bsv_header_generate(&header_size);
|
||||
if (!header)
|
||||
{
|
||||
SSNES_ERR("Failed to generate BSV header!\n");
|
||||
close(new_fd);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t *tmp_header = header;
|
||||
while (header_size)
|
||||
{
|
||||
ssize_t ret = send(new_fd, CONST_CAST tmp_header, header_size, 0);
|
||||
if (ret <= 0)
|
||||
{
|
||||
SSNES_ERR("Failed to send header to client!\n");
|
||||
close(new_fd);
|
||||
free(header);
|
||||
return;
|
||||
}
|
||||
|
||||
header_size -= ret;
|
||||
tmp_header += ret;
|
||||
}
|
||||
|
||||
free(header);
|
||||
handle->spectate_fds[index] = new_fd;
|
||||
}
|
||||
|
||||
void netplay_pre_frame(netplay_t *handle)
|
||||
{
|
||||
if (handle->spectate)
|
||||
netplay_pre_frame_spectate(handle);
|
||||
else
|
||||
netplay_pre_frame_net(handle);
|
||||
}
|
||||
|
||||
static void netplay_post_frame_net(netplay_t *handle)
|
||||
{
|
||||
handle->frame_count++;
|
||||
|
||||
@ -706,7 +949,7 @@ void netplay_post_frame(netplay_t *handle)
|
||||
// Skip ahead if we predicted correctly. Skip until our simulation failed.
|
||||
while (handle->other_frame_count < handle->read_frame_count)
|
||||
{
|
||||
struct delta_frame *ptr = &handle->buffer[handle->other_ptr];
|
||||
const struct delta_frame *ptr = &handle->buffer[handle->other_ptr];
|
||||
if ((ptr->simulated_input_state != ptr->real_input_state) && !ptr->used_real)
|
||||
break;
|
||||
handle->other_ptr = NEXT_PTR(handle->other_ptr);
|
||||
@ -739,3 +982,43 @@ void netplay_post_frame(netplay_t *handle)
|
||||
}
|
||||
}
|
||||
|
||||
static void netplay_post_frame_spectate(netplay_t *handle)
|
||||
{
|
||||
if (handle->spectate_client)
|
||||
return;
|
||||
|
||||
for (unsigned i = 0; i < MAX_SPECTATORS; i++)
|
||||
{
|
||||
if (handle->spectate_fds[i] == -1)
|
||||
continue;
|
||||
|
||||
size_t send_size = handle->spectate_input_ptr * sizeof(int16_t);
|
||||
const uint8_t *tmp_buf = (const uint8_t*)handle->spectate_input;
|
||||
while (send_size)
|
||||
{
|
||||
ssize_t ret = send(handle->spectate_fds[i], CONST_CAST tmp_buf, send_size, 0);
|
||||
if (ret <= 0)
|
||||
{
|
||||
SSNES_LOG("Client disconnected ...\n");
|
||||
close(handle->spectate_fds[i]);
|
||||
handle->spectate_fds[i] = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
tmp_buf += ret;
|
||||
send_size -= ret;
|
||||
}
|
||||
}
|
||||
|
||||
handle->spectate_input_ptr = 0;
|
||||
}
|
||||
|
||||
// Here we check if we have new input and replay from recorded input.
|
||||
void netplay_post_frame(netplay_t *handle)
|
||||
{
|
||||
if (handle->spectate)
|
||||
netplay_post_frame_spectate(handle);
|
||||
else
|
||||
netplay_post_frame_net(handle);
|
||||
}
|
||||
|
||||
|
16
netplay.h
16
netplay.h
@ -28,6 +28,9 @@ 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);
|
||||
|
||||
int16_t input_state_spectate(bool port, unsigned device, unsigned index, unsigned id);
|
||||
int16_t input_state_spectate_client(bool port, unsigned device, unsigned index, unsigned id);
|
||||
|
||||
typedef struct netplay netplay_t;
|
||||
|
||||
struct snes_callbacks
|
||||
@ -38,7 +41,7 @@ struct snes_callbacks
|
||||
};
|
||||
|
||||
// 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);
|
||||
netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const struct snes_callbacks *cb, bool spectate);
|
||||
void netplay_free(netplay_t *handle);
|
||||
|
||||
// Call this before running snes_run()
|
||||
@ -46,15 +49,4 @@ void netplay_pre_frame(netplay_t *handle);
|
||||
// Call this after running snes_run()
|
||||
void netplay_post_frame(netplay_t *handle);
|
||||
|
||||
// Checks if input port/index is controlled by netplay or not.
|
||||
bool netplay_is_alive(netplay_t *handle);
|
||||
|
||||
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);
|
||||
bool netplay_can_poll(netplay_t *handle);
|
||||
const struct snes_callbacks* netplay_callbacks(netplay_t *handle);
|
||||
|
||||
#endif
|
||||
|
55
ssnes.c
55
ssnes.c
@ -492,6 +492,9 @@ static void print_help(void)
|
||||
puts("\t-C/--connect: Connect to netplay as player 2.");
|
||||
puts("\t--port: Port used to netplay. Default is 55435.");
|
||||
puts("\t-F/--frames: Sync frames when using netplay.");
|
||||
puts("\t--spectate: Netplay will become spectacting mode.");
|
||||
puts("\t\tHost can live stream the game content to players that connect.");
|
||||
puts("\t\tHowever, the client will not be able to play. Multiple clients can connect to the host.");
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
@ -640,6 +643,7 @@ static void parse_input(int argc, char *argv[])
|
||||
{ "connect", 1, NULL, 'C' },
|
||||
{ "frames", 1, NULL, 'F' },
|
||||
{ "port", 1, &val, 'p' },
|
||||
{ "spectate", 0, &val, 'S' },
|
||||
#endif
|
||||
{ "ups", 1, NULL, 'U' },
|
||||
{ "bps", 1, &val, 'B' },
|
||||
@ -854,6 +858,10 @@ static void parse_input(int argc, char *argv[])
|
||||
case 'p':
|
||||
g_extern.netplay_port = strtoul(optarg, NULL, 0);
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
g_extern.netplay_is_spectate = true;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case 'B':
|
||||
@ -1221,7 +1229,7 @@ static void init_netplay(void)
|
||||
|
||||
g_extern.netplay = netplay_new(g_extern.netplay_is_client ? g_extern.netplay_server : NULL,
|
||||
g_extern.netplay_port ? g_extern.netplay_port : SSNES_DEFAULT_PORT,
|
||||
g_extern.netplay_sync_frames, &cbs);
|
||||
g_extern.netplay_sync_frames, &cbs, g_extern.netplay_is_spectate);
|
||||
|
||||
if (!g_extern.netplay)
|
||||
{
|
||||
@ -1236,9 +1244,7 @@ static void init_netplay(void)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_NETPLAY
|
||||
static void deinit_netplay(void)
|
||||
{
|
||||
if (g_extern.netplay)
|
||||
@ -1246,6 +1252,34 @@ static void deinit_netplay(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
static void init_libsnes_cbs(void)
|
||||
{
|
||||
#ifdef HAVE_NETPLAY
|
||||
if (g_extern.netplay)
|
||||
{
|
||||
psnes_set_video_refresh(g_extern.netplay_is_spectate ?
|
||||
video_frame : video_frame_net);
|
||||
psnes_set_audio_sample(g_extern.netplay_is_spectate ?
|
||||
audio_sample : audio_sample_net);
|
||||
|
||||
psnes_set_input_state(g_extern.netplay_is_spectate ?
|
||||
(g_extern.netplay_is_client ? input_state_spectate_client : input_state_spectate)
|
||||
: input_state_net);
|
||||
}
|
||||
else
|
||||
{
|
||||
psnes_set_video_refresh(video_frame);
|
||||
psnes_set_audio_sample(audio_sample);
|
||||
psnes_set_input_state(input_state);
|
||||
}
|
||||
#else
|
||||
psnes_set_video_refresh(video_frame);
|
||||
psnes_set_audio_sample(audio_sample);
|
||||
psnes_set_input_state(input_state);
|
||||
#endif
|
||||
psnes_set_input_poll(input_poll);
|
||||
}
|
||||
|
||||
static void init_autosave(void)
|
||||
{
|
||||
#ifdef HAVE_THREADS
|
||||
@ -2058,20 +2092,7 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
init_rewind();
|
||||
|
||||
#ifdef HAVE_NETPLAY
|
||||
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_state(g_extern.netplay ?
|
||||
input_state_net : input_state);
|
||||
#else
|
||||
psnes_set_video_refresh(video_frame);
|
||||
psnes_set_audio_sample(audio_sample);
|
||||
psnes_set_input_state(input_state);
|
||||
#endif
|
||||
psnes_set_input_poll(input_poll);
|
||||
|
||||
init_libsnes_cbs();
|
||||
init_controllers();
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
|
Loading…
Reference in New Issue
Block a user