mirror of
https://github.com/libretro/RetroArch
synced 2025-01-27 12:35:23 +00:00
Merge branch 'master' of github.com:Themaister/SSNES
This commit is contained in:
commit
26cb567bb9
@ -308,6 +308,7 @@ static const struct snes_keybind snes_keybinds_1[] = {
|
||||
{ SSNES_SCREENSHOT, SK_PRINT, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_DSP_CONFIG, SK_c, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_MUTE, SK_F9, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_NETPLAY_FLIP, SK_i, NO_BTN, AXIS_NONE },
|
||||
{ -1 }
|
||||
};
|
||||
|
||||
|
1
driver.h
1
driver.h
@ -57,6 +57,7 @@ enum
|
||||
SSNES_SCREENSHOT,
|
||||
SSNES_DSP_CONFIG,
|
||||
SSNES_MUTE,
|
||||
SSNES_NETPLAY_FLIP,
|
||||
|
||||
#ifdef SSNES_CONSOLE
|
||||
SSNES_CHEAT_INPUT,
|
||||
|
23
getopt.c
23
getopt.c
@ -71,18 +71,29 @@ static int parse_short(const char *optstring, char * const *argv)
|
||||
if (!opt)
|
||||
return '?';
|
||||
|
||||
bool multiple_opt = argv[0][2];
|
||||
bool extra_opt = argv[0][2];
|
||||
bool takes_arg = opt[1] == ':';
|
||||
if (multiple_opt && takes_arg) // Makes little sense to allow.
|
||||
return '?';
|
||||
|
||||
// If we take an argument, and we see additional characters,
|
||||
// this is in fact the argument (i.e. -cfoo is same as -c foo).
|
||||
bool embedded_arg = extra_opt && takes_arg;
|
||||
|
||||
if (takes_arg)
|
||||
{
|
||||
optarg = argv[1];
|
||||
optind += 2;
|
||||
if (embedded_arg)
|
||||
{
|
||||
optarg = argv[0] + 2;
|
||||
optind++;
|
||||
}
|
||||
else
|
||||
{
|
||||
optarg = argv[1];
|
||||
optind += 2;
|
||||
}
|
||||
|
||||
return optarg ? opt[0] : '?';
|
||||
}
|
||||
else if (multiple_opt)
|
||||
else if (embedded_arg) // If we see additional characters, and they don't take arguments, this means we have multiple flags in one.
|
||||
{
|
||||
memmove(&argv[0][1], &argv[0][2], strlen(&argv[0][2]) + 1);
|
||||
return opt[0];
|
||||
|
329
netplay.c
329
netplay.c
@ -54,6 +54,9 @@ 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);
|
||||
|
||||
static bool netplay_send_cmd(netplay_t *handle, uint32_t cmd, const void *data, size_t size);
|
||||
static bool netplay_get_cmd(netplay_t *handle);
|
||||
|
||||
#ifdef _WIN32
|
||||
// Woohoo, Winsock has headers from the STONE AGE! :D
|
||||
#define close(x) closesocket(x)
|
||||
@ -83,12 +86,16 @@ struct delta_frame
|
||||
#define UDP_FRAME_PACKETS 16
|
||||
#define MAX_SPECTATORS 16
|
||||
|
||||
#define NETPLAY_CMD_ACK 0
|
||||
#define NETPLAY_CMD_NAK 1
|
||||
#define NETPLAY_CMD_FLIP_PLAYERS 2
|
||||
|
||||
struct netplay
|
||||
{
|
||||
struct snes_callbacks cbs;
|
||||
int fd; // TCP connection for state sending, etc. Could perhaps be used for messaging later on. :)
|
||||
int fd; // TCP connection for state sending, etc. Also used for commands.
|
||||
int udp_fd; // UDP connection for game state updates.
|
||||
unsigned port; // Which port is governed by netplay?
|
||||
unsigned port; // Which port is governed by netplay (other player)?
|
||||
bool has_connection;
|
||||
|
||||
struct delta_frame *buffer;
|
||||
@ -101,13 +108,14 @@ struct netplay
|
||||
|
||||
size_t state_size;
|
||||
|
||||
size_t is_replay; // Are we replaying old frames?
|
||||
bool is_replay; // Are we replaying old frames?
|
||||
bool can_poll; // We don't want to poll several times on a frame.
|
||||
|
||||
uint32_t packet_buffer[UDP_FRAME_PACKETS * 2]; // To compat UDP packet loss we also send old data along with the packets.
|
||||
uint32_t frame_count;
|
||||
uint32_t read_frame_count;
|
||||
uint32_t other_frame_count;
|
||||
uint32_t tmp_frame_count;
|
||||
struct addrinfo *addr;
|
||||
struct sockaddr_storage their_addr;
|
||||
bool has_client_addr;
|
||||
@ -121,8 +129,48 @@ struct netplay
|
||||
uint16_t *spectate_input;
|
||||
size_t spectate_input_ptr;
|
||||
size_t spectate_input_size;
|
||||
|
||||
// Player flipping
|
||||
// Flipping state. If ptr >= flip_frame, we apply the flip.
|
||||
// If not, we apply the opposite, effectively creating a trigger point.
|
||||
// To avoid collition we need to make sure our client/host is synced up well after flip_frame
|
||||
// before allowing another flip.
|
||||
bool flip;
|
||||
uint32_t flip_frame;
|
||||
};
|
||||
|
||||
static bool send_all(int fd, const void *data_, size_t size)
|
||||
{
|
||||
const uint8_t *data = (const uint8_t*)data_;
|
||||
while (size)
|
||||
{
|
||||
ssize_t ret = send(fd, CONST_CAST data, size, 0);
|
||||
if (ret <= 0)
|
||||
return false;
|
||||
|
||||
data += ret;
|
||||
size -= ret;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool recv_all(int fd, void *data_, size_t size)
|
||||
{
|
||||
uint8_t *data = (uint8_t*)data_;
|
||||
while (size)
|
||||
{
|
||||
ssize_t ret = recv(fd, NONCONST_CAST data, size, 0);
|
||||
if (ret <= 0)
|
||||
return false;
|
||||
|
||||
data += ret;
|
||||
size -= ret;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void warn_hangup(void)
|
||||
{
|
||||
SSNES_WARN("Netplay has disconnected. Will continue without connection ...\n");
|
||||
@ -156,6 +204,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);
|
||||
}
|
||||
|
||||
// Custom inet_ntop. Win32 doesn't seem to support this ...
|
||||
static void log_connection(const struct sockaddr_storage *their_addr, unsigned slot)
|
||||
{
|
||||
union
|
||||
@ -167,29 +216,30 @@ static void log_connection(const struct sockaddr_storage *their_addr, unsigned s
|
||||
u.storage = their_addr;
|
||||
|
||||
const char *str = NULL;
|
||||
char buf_v4[INET_ADDRSTRLEN] = {0};
|
||||
char buf_v6[INET6_ADDRSTRLEN] = {0};
|
||||
|
||||
if (their_addr->ss_family == AF_INET)
|
||||
{
|
||||
char buf[INET_ADDRSTRLEN] = {0};
|
||||
str = buf;
|
||||
str = buf_v4;
|
||||
struct sockaddr_in in;
|
||||
memset(&in, 0, sizeof(in));
|
||||
in.sin_family = AF_INET;
|
||||
memcpy(&in.sin_addr, &u.v4->sin_addr, sizeof(struct in_addr));
|
||||
|
||||
getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in), buf, sizeof(buf),
|
||||
getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in), buf_v4, sizeof(buf_v4),
|
||||
NULL, 0, NI_NUMERICHOST);
|
||||
}
|
||||
else if (their_addr->ss_family == AF_INET6)
|
||||
{
|
||||
char buf[INET6_ADDRSTRLEN] = {0};
|
||||
str = buf;
|
||||
str = buf_v6;
|
||||
struct sockaddr_in6 in;
|
||||
memset(&in, 0, sizeof(in));
|
||||
in.sin6_family = AF_INET6;
|
||||
memcpy(&in.sin6_addr, &u.v6->sin6_addr, sizeof(struct in6_addr));
|
||||
|
||||
getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in6),
|
||||
buf, sizeof(buf), NULL, 0, NI_NUMERICHOST);
|
||||
buf_v6, sizeof(buf_v6), NULL, 0, NI_NUMERICHOST);
|
||||
}
|
||||
|
||||
if (str)
|
||||
@ -197,6 +247,7 @@ static void log_connection(const struct sockaddr_storage *their_addr, unsigned s
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof(msg), "Got connection from: \"%s\" (#%u)", str, slot);
|
||||
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
|
||||
SSNES_LOG("Got connection from: \"%s\" (#%u)\n", str, slot);
|
||||
}
|
||||
}
|
||||
|
||||
@ -391,22 +442,17 @@ static uint32_t implementation_magic_value(void)
|
||||
static bool send_info(netplay_t *handle)
|
||||
{
|
||||
uint32_t header[3] = { htonl(g_extern.cart_crc), htonl(implementation_magic_value()), htonl(psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM)) };
|
||||
if (send(handle->fd, CONST_CAST header, sizeof(header), 0) != sizeof(header))
|
||||
if (!send_all(handle->fd, header, sizeof(header)))
|
||||
return false;
|
||||
|
||||
// Get SRAM data from Player 1 :)
|
||||
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)
|
||||
|
||||
if (!recv_all(handle->fd, sram, sram_size))
|
||||
{
|
||||
ssize_t ret = recv(handle->fd, NONCONST_CAST sram, sram_size, 0);
|
||||
if (ret <= 0)
|
||||
{
|
||||
SSNES_ERR("Failed to receive SRAM data from host.\n");
|
||||
return false;
|
||||
}
|
||||
sram += ret;
|
||||
sram_size -= ret;
|
||||
SSNES_ERR("Failed to receive SRAM data from host.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -415,7 +461,7 @@ static bool send_info(netplay_t *handle)
|
||||
static bool get_info(netplay_t *handle)
|
||||
{
|
||||
uint32_t header[3];
|
||||
if (recv(handle->fd, NONCONST_CAST header, sizeof(header), 0) != sizeof(header))
|
||||
if (!recv_all(handle->fd, header, sizeof(header)))
|
||||
{
|
||||
SSNES_ERR("Failed to receive header from client.\n");
|
||||
return false;
|
||||
@ -439,16 +485,10 @@ 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)
|
||||
if (!send_all(handle->fd, sram, sram_size))
|
||||
{
|
||||
ssize_t ret = send(handle->fd, CONST_CAST sram, sram_size, 0);
|
||||
if (ret <= 0)
|
||||
{
|
||||
SSNES_ERR("Failed to send SRAM data to client.\n");
|
||||
return false;
|
||||
}
|
||||
sram += ret;
|
||||
sram_size -= ret;
|
||||
SSNES_ERR("Failed to send SRAM data to client.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -457,7 +497,7 @@ static bool get_info(netplay_t *handle)
|
||||
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))
|
||||
if (recv_all(handle->fd, header, sizeof(header)))
|
||||
{
|
||||
SSNES_ERR("Cannot get header from host!\n");
|
||||
return false;
|
||||
@ -476,18 +516,12 @@ static bool get_info_spectate(netplay_t *handle)
|
||||
|
||||
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;
|
||||
if (!recv_all(handle->fd, tmp_buf, size))
|
||||
{
|
||||
SSNES_ERR("Failed to receive save state from host!\n");
|
||||
free(tmp_buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = true;
|
||||
@ -630,9 +664,9 @@ static int poll_input(netplay_t *handle, bool block)
|
||||
if (select(max_fd, &fds, NULL, NULL, &tmp_tv) < 0)
|
||||
return -1;
|
||||
|
||||
// Somewhat hacky, but we aren't using the TCP connection for anything useful atm.
|
||||
// Will probably add some proper messaging system here later.
|
||||
if (FD_ISSET(handle->fd, &fds))
|
||||
// Somewhat hacky,
|
||||
// but we aren't using the TCP connection for anything useful atm.
|
||||
if (FD_ISSET(handle->fd, &fds) && !netplay_get_cmd(handle))
|
||||
return -1;
|
||||
|
||||
if (FD_ISSET(handle->udp_fd, &fds))
|
||||
@ -666,14 +700,16 @@ static bool get_self_input_state(netplay_t *handle)
|
||||
if (handle->frame_count > 0) // First frame we always give zero input since relying on input from first frame screws up when we use -F 0.
|
||||
{
|
||||
snes_input_state_t cb = handle->cbs.state_cb;
|
||||
for (unsigned i = 0; i <= 11; i++)
|
||||
for (unsigned i = 0; i < SSNES_FIRST_META_KEY; i++)
|
||||
{
|
||||
int16_t tmp = cb(g_settings.input.netplay_client_swap_input ? 0 : !handle->port, SNES_DEVICE_JOYPAD, 0, i);
|
||||
int16_t tmp = cb(g_settings.input.netplay_client_swap_input ? 0 : !handle->port,
|
||||
SNES_DEVICE_JOYPAD, 0, i);
|
||||
state |= tmp ? 1 << i : 0;
|
||||
}
|
||||
}
|
||||
|
||||
memmove(handle->packet_buffer, handle->packet_buffer + 2, sizeof (handle->packet_buffer) - 2 * sizeof(uint32_t));
|
||||
memmove(handle->packet_buffer, handle->packet_buffer + 2,
|
||||
sizeof (handle->packet_buffer) - 2 * sizeof(uint32_t));
|
||||
handle->packet_buffer[(UDP_FRAME_PACKETS - 1) * 2] = htonl(handle->frame_count);
|
||||
handle->packet_buffer[(UDP_FRAME_PACKETS - 1) * 2 + 1] = htonl(state);
|
||||
|
||||
@ -797,15 +833,155 @@ static bool netplay_poll(netplay_t *handle)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool netplay_send_cmd(netplay_t *handle, uint32_t cmd, const void *data, size_t size)
|
||||
{
|
||||
cmd = (cmd << 16) | (size & 0xffff);
|
||||
cmd = htonl(cmd);
|
||||
|
||||
if (!send_all(handle->fd, &cmd, sizeof(cmd)))
|
||||
return false;
|
||||
|
||||
if (!send_all(handle->fd, data, size))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool netplay_cmd_ack(netplay_t *handle)
|
||||
{
|
||||
uint32_t cmd = htonl(NETPLAY_CMD_ACK);
|
||||
return send_all(handle->fd, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
static bool netplay_cmd_nak(netplay_t *handle)
|
||||
{
|
||||
uint32_t cmd = htonl(NETPLAY_CMD_NAK);
|
||||
return send_all(handle->fd, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
static bool netplay_get_response(netplay_t *handle)
|
||||
{
|
||||
uint32_t response;
|
||||
if (!recv_all(handle->fd, &response, sizeof(response)))
|
||||
return false;
|
||||
|
||||
return ntohl(response) == NETPLAY_CMD_ACK;
|
||||
}
|
||||
|
||||
static bool netplay_get_cmd(netplay_t *handle)
|
||||
{
|
||||
uint32_t cmd;
|
||||
if (!recv_all(handle->fd, &cmd, sizeof(cmd)))
|
||||
return false;
|
||||
|
||||
cmd = ntohl(cmd);
|
||||
|
||||
size_t cmd_size = cmd & 0xffff;
|
||||
cmd = cmd >> 16;
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case NETPLAY_CMD_FLIP_PLAYERS:
|
||||
{
|
||||
if (cmd_size != sizeof(uint32_t))
|
||||
{
|
||||
SSNES_ERR("CMD_FLIP_PLAYERS has unexpected command size!\n");
|
||||
return netplay_cmd_nak(handle);
|
||||
}
|
||||
|
||||
uint32_t flip_frame;
|
||||
if (!recv_all(handle->fd, &flip_frame, sizeof(flip_frame)))
|
||||
{
|
||||
SSNES_ERR("Failed to receive CMD_FLIP_PLAYERS argument!\n");
|
||||
return netplay_cmd_nak(handle);
|
||||
}
|
||||
|
||||
flip_frame = ntohl(flip_frame);
|
||||
if (flip_frame < handle->flip_frame)
|
||||
{
|
||||
SSNES_ERR("Host asked us to flip players in the past. Not possible ...\n");
|
||||
return netplay_cmd_nak(handle);
|
||||
}
|
||||
|
||||
handle->flip ^= true;
|
||||
handle->flip_frame = flip_frame;
|
||||
|
||||
SSNES_LOG("Netplay players are flipped!\n");
|
||||
msg_queue_push(g_extern.msg_queue, "Netplay players are flipped!", 1, 180);
|
||||
|
||||
return netplay_cmd_ack(handle);
|
||||
}
|
||||
|
||||
default:
|
||||
SSNES_ERR("Unknown netplay command received!\n");
|
||||
return netplay_cmd_nak(handle);
|
||||
}
|
||||
}
|
||||
|
||||
void netplay_flip_players(netplay_t *handle)
|
||||
{
|
||||
uint32_t flip_frame = handle->frame_count + 2 * UDP_FRAME_PACKETS;
|
||||
uint32_t flip_frame_net = htonl(flip_frame);
|
||||
const char *msg = NULL;
|
||||
|
||||
if (handle->spectate)
|
||||
{
|
||||
msg = "Cannot flip players in spectate mode!";
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (handle->port == 0)
|
||||
{
|
||||
msg = "Cannot flip players if you're not the host!";
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Make sure both clients are definitely synced up.
|
||||
if (handle->frame_count < (handle->flip_frame + 2 * UDP_FRAME_PACKETS))
|
||||
{
|
||||
msg = "Cannot flip players yet! Wait a second or two before attempting flip.";
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (netplay_send_cmd(handle, NETPLAY_CMD_FLIP_PLAYERS, &flip_frame_net, sizeof(flip_frame_net))
|
||||
&& netplay_get_response(handle))
|
||||
{
|
||||
SSNES_LOG("Netplay players are flipped!\n");
|
||||
msg_queue_push(g_extern.msg_queue, "Netplay players are flipped!", 1, 180);
|
||||
|
||||
// Queue up a flip well enough in the future.
|
||||
handle->flip ^= true;
|
||||
handle->flip_frame = flip_frame;
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = "Failed to flip players!";
|
||||
goto error;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
SSNES_WARN("%s\n", msg);
|
||||
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
|
||||
}
|
||||
|
||||
static bool netplay_flip_port(netplay_t *handle, bool port)
|
||||
{
|
||||
if (handle->flip_frame == 0)
|
||||
return port;
|
||||
|
||||
size_t frame = handle->is_replay ? handle->tmp_frame_count : handle->frame_count;
|
||||
|
||||
return port ^ handle->flip ^ (frame < handle->flip_frame);
|
||||
}
|
||||
|
||||
int16_t netplay_input_state(netplay_t *handle, bool port, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
uint16_t input_state = 0;
|
||||
size_t ptr = 0;
|
||||
size_t ptr = handle->is_replay ? handle->tmp_ptr : PREV_PTR(handle->self_ptr);
|
||||
|
||||
if (handle->is_replay)
|
||||
ptr = handle->tmp_ptr;
|
||||
else
|
||||
ptr = PREV_PTR(handle->self_ptr);
|
||||
port = netplay_flip_port(handle, port);
|
||||
|
||||
if ((port ? 1 : 0) == handle->port)
|
||||
{
|
||||
@ -897,7 +1073,7 @@ int16_t input_state_spectate(bool port, unsigned device, unsigned index, unsigne
|
||||
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))
|
||||
if (recv_all(handle->fd, NONCONST_CAST &inp, sizeof(inp)))
|
||||
return swap_if_big16(inp);
|
||||
else
|
||||
{
|
||||
@ -970,19 +1146,12 @@ static void netplay_pre_frame_spectate(netplay_t *handle)
|
||||
setsockopt(new_fd, SOL_SOCKET, SO_SNDBUF, CONST_CAST &bufsize, sizeof(int));
|
||||
|
||||
const uint8_t *tmp_header = header;
|
||||
while (header_size)
|
||||
if (!send_all(new_fd, tmp_header, 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;
|
||||
SSNES_ERR("Failed to send header to client!\n");
|
||||
close(new_fd);
|
||||
free(header);
|
||||
return;
|
||||
}
|
||||
|
||||
free(header);
|
||||
@ -1022,6 +1191,8 @@ static void netplay_post_frame_net(netplay_t *handle)
|
||||
// Replay frames
|
||||
handle->is_replay = true;
|
||||
handle->tmp_ptr = handle->other_ptr;
|
||||
handle->tmp_frame_count = handle->other_frame_count;
|
||||
|
||||
psnes_unserialize(handle->buffer[handle->other_ptr].state, handle->state_size);
|
||||
bool first = true;
|
||||
while (first || (handle->tmp_ptr != handle->self_ptr))
|
||||
@ -1035,8 +1206,10 @@ static void netplay_post_frame_net(netplay_t *handle)
|
||||
unlock_autosave();
|
||||
#endif
|
||||
handle->tmp_ptr = NEXT_PTR(handle->tmp_ptr);
|
||||
handle->tmp_frame_count++;
|
||||
first = false;
|
||||
}
|
||||
|
||||
handle->other_ptr = handle->read_ptr;
|
||||
handle->other_frame_count = handle->read_frame_count;
|
||||
handle->is_replay = false;
|
||||
@ -1053,26 +1226,18 @@ static void netplay_post_frame_spectate(netplay_t *handle)
|
||||
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)
|
||||
if (!send_all(handle->spectate_fds[i],
|
||||
handle->spectate_input, handle->spectate_input_ptr * sizeof(int16_t)))
|
||||
{
|
||||
ssize_t ret = send(handle->spectate_fds[i], CONST_CAST tmp_buf, send_size, 0);
|
||||
if (ret <= 0)
|
||||
{
|
||||
SSNES_LOG("Client (#%u) disconnected ...\n", i);
|
||||
SSNES_LOG("Client (#%u) disconnected ...\n", i);
|
||||
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof(msg), "Client (#%u) disconnected!", i);
|
||||
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof(msg), "Client (#%u) disconnected!", i);
|
||||
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
|
||||
|
||||
close(handle->spectate_fds[i]);
|
||||
handle->spectate_fds[i] = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
tmp_buf += ret;
|
||||
send_size -= ret;
|
||||
close(handle->spectate_fds[i]);
|
||||
handle->spectate_fds[i] = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,9 +41,14 @@ 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, bool spectate);
|
||||
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);
|
||||
|
||||
// On regular netplay, flip who controls player 1 and 2.
|
||||
void netplay_flip_players(netplay_t *handle);
|
||||
|
||||
// Call this before running snes_run()
|
||||
void netplay_pre_frame(netplay_t *handle);
|
||||
// Call this after running snes_run()
|
||||
|
@ -555,6 +555,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS] = {
|
||||
DECLARE_BIND(screenshot, SSNES_SCREENSHOT),
|
||||
DECLARE_BIND(dsp_config, SSNES_DSP_CONFIG),
|
||||
DECLARE_BIND(audio_mute, SSNES_MUTE),
|
||||
DECLARE_BIND(netplay_flip_players, SSNES_NETPLAY_FLIP),
|
||||
DECLARE_BIND_END(),
|
||||
},
|
||||
|
||||
|
23
ssnes.c
23
ssnes.c
@ -95,7 +95,6 @@ static inline unsigned lines_to_pitch(unsigned height)
|
||||
return g_extern.system.pitch;
|
||||
}
|
||||
|
||||
static void video_cached_frame(void);
|
||||
static void take_screenshot(void)
|
||||
{
|
||||
if (!(*g_settings.screenshot_directory))
|
||||
@ -130,7 +129,7 @@ static void take_screenshot(void)
|
||||
if (g_extern.is_paused)
|
||||
{
|
||||
msg_queue_push(g_extern.msg_queue, msg, 1, 1);
|
||||
video_cached_frame();
|
||||
ssnes_render_cached_frame();
|
||||
}
|
||||
else
|
||||
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
|
||||
@ -222,7 +221,7 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
|
||||
g_extern.frame_cache.height = height;
|
||||
}
|
||||
|
||||
static void video_cached_frame(void)
|
||||
void ssnes_render_cached_frame(void)
|
||||
{
|
||||
#ifdef HAVE_FFMPEG
|
||||
// Cannot allow FFmpeg recording when pushing duped frames.
|
||||
@ -235,8 +234,6 @@ static void video_cached_frame(void)
|
||||
// It would be really stupid at any rate ...
|
||||
if (g_extern.frame_cache.data)
|
||||
{
|
||||
// Push the pipeline through. A hack sort of.
|
||||
SSNES_LOG("Pushing cached frame.\n");
|
||||
video_frame(g_extern.frame_cache.data,
|
||||
g_extern.frame_cache.width,
|
||||
g_extern.frame_cache.height);
|
||||
@ -1973,10 +1970,17 @@ static void check_mute(void)
|
||||
old_pressed = pressed;
|
||||
}
|
||||
|
||||
void ssnes_render_cached_frame(void)
|
||||
#ifdef HAVE_NETPLAY
|
||||
static void check_netplay_flip(void)
|
||||
{
|
||||
video_cached_frame();
|
||||
static bool old_pressed = false;
|
||||
bool pressed = driver.input->key_pressed(driver.input_data, SSNES_NETPLAY_FLIP);
|
||||
if (pressed && !old_pressed)
|
||||
netplay_flip_players(g_extern.netplay);
|
||||
|
||||
old_pressed = pressed;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void do_state_checks(void)
|
||||
{
|
||||
@ -1991,7 +1995,7 @@ static void do_state_checks(void)
|
||||
check_oneshot();
|
||||
|
||||
if (check_fullscreen() && g_extern.is_paused)
|
||||
video_cached_frame();
|
||||
ssnes_render_cached_frame();
|
||||
|
||||
if (g_extern.is_paused && !g_extern.is_oneshot)
|
||||
return;
|
||||
@ -2025,7 +2029,10 @@ static void do_state_checks(void)
|
||||
#ifdef HAVE_NETPLAY
|
||||
}
|
||||
else
|
||||
{
|
||||
check_netplay_flip();
|
||||
check_fullscreen();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_DYLIB
|
||||
|
@ -317,6 +317,9 @@
|
||||
# Take screenshot
|
||||
# input_screenshot = print_screen
|
||||
|
||||
# Netplay flip players.
|
||||
# input_netplay_flip_players = i
|
||||
|
||||
# Directory to dump screenshots to.
|
||||
# screenshot_directory =
|
||||
|
||||
|
@ -103,6 +103,7 @@ static struct bind binds[] = {
|
||||
MISC_BIND("Screenshot", screenshot)
|
||||
MISC_BIND("DSP config", dsp_config)
|
||||
MISC_BIND("Audio mute/unmute", audio_mute)
|
||||
MISC_BIND("Netplay player flip", netplay_flip_players)
|
||||
};
|
||||
|
||||
static void get_binds(config_file_t *conf, int player, int joypad)
|
||||
|
Loading…
x
Reference in New Issue
Block a user