diff --git a/docs/ssnes.1 b/docs/ssnes.1 index bd24fce492..fc3917cc85 100644 --- a/docs/ssnes.1 +++ b/docs/ssnes.1 @@ -177,6 +177,11 @@ 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--nick NICK\fR +Pick a nickname for use with netplay. +This is purely cosmetic, and only serves to help players identify each other. + .TP \fB--ups PATCH, -U PATCH\fR Attempts to apply an UPS patch to the current ROM image. No files are altered. diff --git a/general.h b/general.h index 97b2b66bd6..361fd9d768 100644 --- a/general.h +++ b/general.h @@ -333,6 +333,7 @@ struct global bool netplay_is_spectate; unsigned netplay_sync_frames; uint16_t netplay_port; + char netplay_nick[32]; #endif // FFmpeg record. diff --git a/movie.c b/movie.c index 6ddb452fb0..e44e4fb61a 100644 --- a/movie.c +++ b/movie.c @@ -102,7 +102,7 @@ struct bsv_movie #define BSV_MAGIC 0x42535631 #define MAGIC_INDEX 0 -#define SERIALIZER_INDEX 1 // Not current used. +#define SERIALIZER_INDEX 1 #define CRC_INDEX 2 #define STATE_SIZE_INDEX 3 @@ -329,22 +329,23 @@ void bsv_movie_frame_rewind(bsv_movie_t *handle) } } -uint8_t *bsv_header_generate(size_t *size) +uint32_t *bsv_header_generate(size_t *size, uint32_t magic) { 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); + uint32_t *header = (uint32_t*)malloc(header_size); if (!header) return NULL; bsv_header[MAGIC_INDEX] = swap_if_little32(BSV_MAGIC); + bsv_header[SERIALIZER_INDEX] = swap_if_big32(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)) + if (serialize_size && !psnes_serialize((uint8_t*)header + sizeof(bsv_header), serialize_size)) { free(header); return NULL; @@ -354,7 +355,7 @@ uint8_t *bsv_header_generate(size_t *size) return header; } -bool bsv_parse_header(const uint32_t *header) +bool bsv_parse_header(const uint32_t *header, uint32_t magic) { uint32_t in_bsv = swap_if_little32(header[MAGIC_INDEX]); if (in_bsv != BSV_MAGIC) @@ -364,6 +365,13 @@ bool bsv_parse_header(const uint32_t *header) return false; } + uint32_t in_magic = swap_if_big32(header[SERIALIZER_INDEX]); + if (in_magic != magic) + { + SSNES_ERR("Magic mismatch, got 0x%x, expected 0x%x!\n", in_magic, magic); + return false; + } + uint32_t in_crc = swap_if_big32(header[CRC_INDEX]); if (in_crc != g_extern.cart_crc) { diff --git a/movie.h b/movie.h index f3ff48dfe6..8d95e5b9a8 100644 --- a/movie.h +++ b/movie.h @@ -30,8 +30,8 @@ enum ssnes_movie_type SSNES_MOVIE_RECORD }; -uint8_t *bsv_header_generate(size_t *size); -bool bsv_parse_header(const uint32_t *header); +uint32_t *bsv_header_generate(size_t *size, uint32_t magic); +bool bsv_parse_header(const uint32_t *header, uint32_t magic); bsv_movie_t *bsv_movie_init(const char *path, enum ssnes_movie_type type); diff --git a/netplay.c b/netplay.c index 0f26e9448f..75ac47bd87 100644 --- a/netplay.c +++ b/netplay.c @@ -92,6 +92,10 @@ struct delta_frame struct netplay { + char nick[32]; + char other_nick[32]; + struct sockaddr_storage other_addr; + struct snes_callbacks cbs; int fd; // TCP connection for state sending, etc. Also used for commands. int udp_fd; // UDP connection for game state updates. @@ -205,7 +209,8 @@ int16_t input_state_net(bool port, unsigned device, unsigned index, unsigned id) } // Custom inet_ntop. Win32 doesn't seem to support this ... -static void log_connection(const struct sockaddr_storage *their_addr, unsigned slot) +static void log_connection(const struct sockaddr_storage *their_addr, + unsigned slot, const char *nick) { union { @@ -245,9 +250,9 @@ static void log_connection(const struct sockaddr_storage *their_addr, unsigned s if (str) { char msg[512]; - snprintf(msg, sizeof(msg), "Got connection from: \"%s\" (#%u)", str, slot); + snprintf(msg, sizeof(msg), "Got connection from: \"%s (%s)\" (#%u)", nick, str, slot); msg_queue_push(g_extern.msg_queue, msg, 1, 180); - SSNES_LOG("Got connection from: \"%s\" (#%u)\n", str, slot); + SSNES_LOG("%s\n", msg); } } @@ -323,9 +328,9 @@ static bool init_tcp_socket(netplay_t *handle, const char *server, uint16_t port return false; } - struct sockaddr_storage their_addr; - socklen_t addr_size = sizeof(their_addr); - int new_fd = accept(handle->fd, (struct sockaddr*)&their_addr, &addr_size); + socklen_t addr_size = sizeof(handle->other_addr); + int new_fd = accept(handle->fd, + (struct sockaddr*)&handle->other_addr, &addr_size); if (new_fd < 0) { SSNES_ERR("Failed to accept socket.\n"); @@ -336,8 +341,6 @@ static bool init_tcp_socket(netplay_t *handle, const char *server, uint16_t port } close(handle->fd); handle->fd = new_fd; - - log_connection(&their_addr, 0); } freeaddrinfo(res); @@ -427,8 +430,11 @@ bool netplay_can_poll(netplay_t *handle) static uint32_t implementation_magic_value(void) { uint32_t res = 0; - res |= (psnes_library_revision_major() & 0xf) << 0; - res |= (psnes_library_revision_minor() & 0xf) << 4; + unsigned major = psnes_library_revision_major(); + unsigned minor = psnes_library_revision_minor(); + + res |= (major & 0xf) << 0; + res |= (minor & 0xf) << 4; // Shouldn't really use this, but oh well :) It'll do the job. const char *lib = psnes_library_id(); @@ -436,15 +442,75 @@ static uint32_t implementation_magic_value(void) for (size_t i = 0; i < len; i++) res ^= lib[i] << (i & 0xf); + const char *ver = PACKAGE_VERSION; + len = strlen(ver); + for (size_t i = 0; i < len; i++) + res ^= ver[i] << ((i & 0xf) + 16); + return res; } +static bool send_nickname(netplay_t *handle) +{ + uint8_t nick_size = strlen(handle->nick); + + if (!send_all(handle->fd, &nick_size, sizeof(nick_size))) + { + SSNES_ERR("Failed to send nick size.\n"); + return false; + } + + if (!send_all(handle->fd, handle->nick, nick_size)) + { + SSNES_ERR("Failed to send nick.\n"); + return false; + } + + return true; +} + +static bool get_nickname(netplay_t *handle) +{ + uint8_t nick_size; + + if (!recv_all(handle->fd, &nick_size, sizeof(nick_size))) + { + SSNES_ERR("Failed to receive nick size from host.\n"); + return false; + } + + if (nick_size >= sizeof(handle->other_nick)) + { + SSNES_ERR("Invalid nick size.\n"); + return false; + } + + if (!recv_all(handle->fd, handle->other_nick, nick_size)) + { + SSNES_ERR("Failed to receive nick.\n"); + return false; + } + + return true; +} + 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)) }; + uint32_t header[3] = { + htonl(g_extern.cart_crc), + htonl(implementation_magic_value()), + htonl(psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM)) + }; + if (!send_all(handle->fd, header, sizeof(header))) return false; + if (!send_nickname(handle)) + { + SSNES_ERR("Failed to send nick to host.\n"); + 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); @@ -455,33 +521,54 @@ static bool send_info(netplay_t *handle) return false; } + if (!get_nickname(handle)) + { + SSNES_ERR("Failed to receive nick from host.\n"); + return false; + } + + char msg[512]; + snprintf(msg, sizeof(msg), "Connected to: \"%s\"", handle->other_nick); + SSNES_LOG("%s\n", msg); + msg_queue_push(g_extern.msg_queue, msg, 1, 180); + return true; } static bool get_info(netplay_t *handle) { uint32_t header[3]; + if (!recv_all(handle->fd, header, sizeof(header))) { SSNES_ERR("Failed to receive header from client.\n"); return false; } + if (g_extern.cart_crc != ntohl(header[0])) { SSNES_ERR("Cart CRC32s differ! Cannot use different games!\n"); return false; } + if (implementation_magic_value() != ntohl(header[1])) { - SSNES_ERR("Implementations differ, make sure you're using exact same libsnes implementations!\n"); + SSNES_ERR("Implementations differ, make sure you're using exact same libsnes implementations and SSNES version!\n"); return false; } + if (psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM) != ntohl(header[2])) { SSNES_ERR("Cartridge SRAM sizes do not correspond!\n"); return false; } + if (!get_nickname(handle)) + { + SSNES_ERR("Failed to get nickname from client.\n"); + return false; + } + // 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); @@ -491,20 +578,29 @@ static bool get_info(netplay_t *handle) return false; } + if (!send_nickname(handle)) + { + SSNES_ERR("Failed to send nickname to client.\n"); + return false; + } + + log_connection(&handle->other_addr, 0, handle->other_nick); + return true; } static bool get_info_spectate(netplay_t *handle) { uint32_t header[4]; - if (recv_all(handle->fd, header, sizeof(header))) + + if (!recv_all(handle->fd, header, sizeof(header))) { SSNES_ERR("Cannot get header from host!\n"); return false; } unsigned save_state_size = psnes_serialize_size(); - if (!bsv_parse_header(header)) + if (!bsv_parse_header(header, implementation_magic_value())) { SSNES_ERR("Received invalid BSV header from host!\n"); return false; @@ -543,7 +639,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, bool spectate) +netplay_t *netplay_new(const char *server, uint16_t port, + unsigned frames, const struct snes_callbacks *cb, + bool spectate, + const char *nick) { (void)spectate; @@ -560,6 +659,7 @@ netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const handle->port = server ? 0 : 1; handle->spectate = spectate; handle->spectate_client = server != NULL; + strlcpy(handle->nick, nick, sizeof(handle->nick)); if (!init_socket(handle, server, port)) { @@ -1134,7 +1234,7 @@ static void netplay_pre_frame_spectate(netplay_t *handle) } size_t header_size; - uint8_t *header = bsv_header_generate(&header_size); + uint32_t *header = bsv_header_generate(&header_size, implementation_magic_value()); if (!header) { SSNES_ERR("Failed to generate BSV header!\n"); @@ -1145,8 +1245,7 @@ static void netplay_pre_frame_spectate(netplay_t *handle) int bufsize = header_size; setsockopt(new_fd, SOL_SOCKET, SO_SNDBUF, CONST_CAST &bufsize, sizeof(int)); - const uint8_t *tmp_header = header; - if (!send_all(new_fd, tmp_header, header_size)) + if (!send_all(new_fd, header, header_size)) { SSNES_ERR("Failed to send header to client!\n"); close(new_fd); @@ -1157,7 +1256,7 @@ static void netplay_pre_frame_spectate(netplay_t *handle) free(header); handle->spectate_fds[index] = new_fd; - log_connection(&their_addr, index); + log_connection(&their_addr, index, handle->other_nick); } void netplay_pre_frame(netplay_t *handle) diff --git a/netplay.h b/netplay.h index 3e8ada5ded..d05189a96e 100644 --- a/netplay.h +++ b/netplay.h @@ -43,7 +43,8 @@ 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); + const struct snes_callbacks *cb, bool spectate, + const char *nick); void netplay_free(netplay_t *handle); // On regular netplay, flip who controls player 1 and 2. diff --git a/ssnes.c b/ssnes.c index fffe7885a8..cde4c0348d 100644 --- a/ssnes.c +++ b/ssnes.c @@ -495,6 +495,7 @@ static void print_help(void) 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."); + puts("\t--nick: Picks a nickname for use with netplay. Not mandatory."); #endif #ifdef HAVE_FFMPEG @@ -644,6 +645,7 @@ static void parse_input(int argc, char *argv[]) { "frames", 1, NULL, 'F' }, { "port", 1, &val, 'p' }, { "spectate", 0, &val, 'S' }, + { "nick", 1, &val, 'N' }, #endif { "ups", 1, NULL, 'U' }, { "bps", 1, &val, 'B' }, @@ -862,6 +864,10 @@ static void parse_input(int argc, char *argv[]) case 'S': g_extern.netplay_is_spectate = true; break; + + case 'N': + strlcpy(g_extern.netplay_nick, optarg, sizeof(g_extern.netplay_nick)); + break; #endif case 'B': @@ -1229,7 +1235,8 @@ 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_is_spectate); + g_extern.netplay_sync_frames, &cbs, g_extern.netplay_is_spectate, + g_extern.netplay_nick); if (!g_extern.netplay) {