From 648e74438bda250ece168ba1d09b1d0b589bf549 Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Thu, 9 Nov 2023 23:26:56 +0900 Subject: [PATCH 1/2] Updates to the netplay netpacket interface - Switch environment call number from 76 to 78 (retire 76 as it was never used by any core) - Simplify broadcasts by removing the option to send to all but one client - Separate explicit flushing and querying of incoming packet into two operations (RETRO_NETPACKET_FLUSH_HINT and retro_netpacket_poll_receive_t) - Enable a core to specify a protocol version string which can get used instead of core version to determine compatibility between two players - Log and notify a separate message when there is a content crc mismsatch to convey it being less severe (as different games may be able to do network communication with each other) --- intl/msg_hash_us.h | 4 + libretro-common/include/libretro.h | 93 +++++++++++++-------- msg_hash.h | 1 + network/netplay/netplay_frontend.c | 128 ++++++++++++++++++----------- network/netplay/netplay_private.h | 3 - 5 files changed, 143 insertions(+), 86 deletions(-) diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index a49685919b3b..07cddf6b20d0 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -13760,6 +13760,10 @@ MSG_HASH( MSG_CONTENT_CRC32S_DIFFER, "Content CRC32s differ. Cannot use different games." ) +MSG_HASH( + MSG_CONTENT_NETPACKET_CRC32S_DIFFER, + "Host is running a different game." + ) MSG_HASH( MSG_PING_TOO_HIGH, "Your ping is too high for this host." diff --git a/libretro-common/include/libretro.h b/libretro-common/include/libretro.h index cb1c2d25ef96..b16228b272d6 100644 --- a/libretro-common/include/libretro.h +++ b/libretro-common/include/libretro.h @@ -1810,7 +1810,26 @@ enum retro_mod * even before the microphone driver is ready. */ -#define RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE 76 + /* Environment 76 was an obsolete version of RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE. + * It was not used by any known core at the time, and was removed from the API. */ + +#define RETRO_ENVIRONMENT_GET_DEVICE_POWER (77 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_device_power * -- + * Returns the device's current power state as reported by the frontend. + * This is useful for emulating the battery level in handheld consoles, + * or for reducing power consumption when on battery power. + * + * The return value indicates whether the frontend can provide this information, + * even if the parameter is NULL. + * + * If the frontend does not support this functionality, + * then the provided argument will remain unchanged. + * + * Note that this environment call describes the power state for the entire device, + * not for individual peripherals like controllers. + */ + +#define RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE 78 /* const struct retro_netpacket_callback * -- * When set, a core gains control over network packets sent and * received during a multiplayer session. This can be used to @@ -1834,22 +1853,6 @@ enum retro_mod * input devices does not need to take any action on its own. */ -#define RETRO_ENVIRONMENT_GET_DEVICE_POWER (77 | RETRO_ENVIRONMENT_EXPERIMENTAL) - /* struct retro_device_power * -- - * Returns the device's current power state as reported by the frontend. - * This is useful for emulating the battery level in handheld consoles, - * or for reducing power consumption when on battery power. - * - * The return value indicates whether the frontend can provide this information, - * even if the parameter is NULL. - * - * If the frontend does not support this functionality, - * then the provided argument will remain unchanged. - * - * Note that this environment call describes the power state for the entire device, - * not for individual peripherals like controllers. - */ - /* VFS functionality */ /* File paths: @@ -3078,32 +3081,44 @@ struct retro_disk_control_ext_callback /* Netpacket flags for retro_netpacket_send_t */ #define RETRO_NETPACKET_UNRELIABLE 0 /* Packet to be sent unreliable, depending on network quality it might not arrive. */ -#define RETRO_NETPACKET_RELIABLE (1 << 0) /* Reliable packets are guaranteed to arrive at the target in the order they were send. */ +#define RETRO_NETPACKET_RELIABLE (1 << 0) /* Reliable packets are guaranteed to arrive at the target in the order they were sent. */ #define RETRO_NETPACKET_UNSEQUENCED (1 << 1) /* Packet will not be sequenced with other packets and may arrive out of order. Cannot be set on reliable packets. */ +#define RETRO_NETPACKET_FLUSH_HINT (1 << 2) /* Request the packet and any previously buffered ones to be sent immediately */ -/* Used by the core to send a packet to one or more connected players. +/* Broadcast client_id for retro_netpacket_send_t */ +#define RETRO_NETPACKET_BROADCAST 0xFFFF + +/* Used by the core to send a packet to one or all connected players. * A single packet sent via this interface can contain up to 64 KB of data. * - * The broadcast flag can be set to true to send to multiple connected clients. - * In a broadcast, the client_id argument indicates 1 client NOT to send the - * packet to (pass 0xFFFF to send to everyone). Otherwise, the client_id - * argument indicates a single client to send the packet to. + * The client_id RETRO_NETPACKET_BROADCAST sends the packet as a broadcast to + * all connected players. This is supported from the host as well as clients. +* Otherwise, the argument indicates the player to send the packet to. * * A frontend must support sending reliable packets (RETRO_NETPACKET_RELIABLE). * Unreliable packets might not be supported by the frontend, but the flags can * still be specified. Reliable transmission will be used instead. * - * If this function is called passing NULL for buf, it will instead flush all - * previously buffered outgoing packets and instantly read any incoming packets. - * During such a call, retro_netpacket_receive_t and retro_netpacket_stop_t can - * be called. The core can perform this in a loop to do a blocking read, i.e., - * wait for incoming data, but needs to handle stop getting called and also - * give up after a short while to avoid freezing on a connection problem. + * Calling this with the flag RETRO_NETPACKET_FLUSH_HINT will send off the + * packet and any previously buffered ones immediately and without blocking. + * To only flush previously queued packets, buf or len can be passed as NULL/0. * * This function is not guaranteed to be thread-safe and must be called during * retro_run or any of the netpacket callbacks passed with this interface. */ -typedef void (RETRO_CALLCONV *retro_netpacket_send_t)(int flags, const void* buf, size_t len, uint16_t client_id, bool broadcast); +typedef void (RETRO_CALLCONV *retro_netpacket_send_t)(int flags, const void* buf, size_t len, uint16_t client_id); + +/* Optionally read any incoming packets without waiting for the end of the + * frame. While polling, retro_netpacket_receive_t and retro_netpacket_stop_t + * can be called. The core can perform this in a loop to do a blocking read, + * i.e., wait for incoming data, but needs to handle stop getting called and + * also give up after a short while to avoid freezing on a connection problem. + * It is a good idea to manually flush outgoing packets before calling this. + * + * This function is not guaranteed to be thread-safe and must be called during + * retro_run or any of the netpacket callbacks passed with this interface. + */ +typedef void (RETRO_CALLCONV *retro_netpacket_poll_receive_t)(); /* Called by the frontend to signify that a multiplayer session has started. * If client_id is 0 the local player is the host of the session and at this @@ -3112,11 +3127,12 @@ typedef void (RETRO_CALLCONV *retro_netpacket_send_t)(int flags, const void* buf * If client_id is > 0 the local player is a client connected to a host and * at this point is already fully connected to the host. * - * The core must store the retro_netpacket_send_t function pointer provided - * here and use it whenever it wants to send a packet. This function pointer - * remains valid until the frontend calls retro_netpacket_stop_t. + * The core must store the function pointer send_fn and use it whenever it + * wants to send a packet. Optionally poll_receive_fn can be stored and used + * when regular receiving between frames is not enough. These function pointers + * remain valid until the frontend calls retro_netpacket_stop_t. */ -typedef void (RETRO_CALLCONV *retro_netpacket_start_t)(uint16_t client_id, retro_netpacket_send_t send_fn); +typedef void (RETRO_CALLCONV *retro_netpacket_start_t)(uint16_t client_id, retro_netpacket_send_t send_fn, retro_netpacket_poll_receive_t poll_receive_fn); /* Called by the frontend when a new packet arrives which has been sent from * another player with retro_netpacket_send_t. The client_id argument indicates @@ -3125,8 +3141,8 @@ typedef void (RETRO_CALLCONV *retro_netpacket_start_t)(uint16_t client_id, retro typedef void (RETRO_CALLCONV *retro_netpacket_receive_t)(const void* buf, size_t len, uint16_t client_id); /* Called by the frontend when the multiplayer session has ended. - * Once this gets called the retro_netpacket_send_t function pointer passed - * to retro_netpacket_start_t will not be valid anymore. + * Once this gets called the function pointers passed to + * retro_netpacket_start_t will not be valid anymore. */ typedef void (RETRO_CALLCONV *retro_netpacket_stop_t)(void); @@ -3153,6 +3169,10 @@ typedef void (RETRO_CALLCONV *retro_netpacket_disconnected_t)(uint16_t client_id * network packets during a multiplayer session between two or more instances * of a libretro frontend. * + * Normally during connection handshake the frontend will compare library_version + * used by both parties and show a warning if there is a difference. When the core + * supplies protocol_version, the frontend will check against this instead. + * * @see RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE */ struct retro_netpacket_callback @@ -3163,6 +3183,7 @@ struct retro_netpacket_callback retro_netpacket_poll_t poll; /* Optional - may be NULL */ retro_netpacket_connected_t connected; /* Optional - may be NULL */ retro_netpacket_disconnected_t disconnected; /* Optional - may be NULL */ + const char* protocol_version; /* Optional - if not NULL will be used instead of core version to decide if communication is compatible */ }; enum retro_pixel_format diff --git a/msg_hash.h b/msg_hash.h index 02a092fd3124..e1776cd664be 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -369,6 +369,7 @@ enum msg_hash_enums MSG_COULD_NOT_FIND_ANY_NEXT_DRIVER, MSG_MOVIE_FORMAT_DIFFERENT_SERIALIZER_VERSION, MSG_CONTENT_CRC32S_DIFFER, + MSG_CONTENT_NETPACKET_CRC32S_DIFFER, MSG_PING_TOO_HIGH, MSG_RECORDING_TERMINATED_DUE_TO_RESIZE, MSG_FAILED_TO_START_RECORDING, diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 054e82536ae0..6987ee4f1218 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -702,9 +702,10 @@ static uint32_t simple_rand_uint32(unsigned long *simple_rand_next) } static void netplay_send_cmd_netpacket(netplay_t *netplay, size_t conn_i, - const void* buf, size_t len, uint16_t client_id, bool broadcast); + const void* buf, size_t len, uint16_t client_id); static void RETRO_CALLCONV netplay_netpacket_send_cb(int flags, - const void* buf, size_t len, uint16_t client_id, bool broadcast); + const void* buf, size_t len, uint16_t client_id); +static void RETRO_CALLCONV netplay_netpacket_poll_receive_cb(); /* * netplay_init_socket_buffer @@ -1191,6 +1192,16 @@ static bool netplay_handshake_info(netplay_t *netplay, "UNKNOWN", sizeof(info_buf.core_version)); } + /* When using the netpacket interface, the core can override the version + * with a custom string so multiple core versions can stay compatible. */ + if (networking_driver_st.core_netpacket_interface && + networking_driver_st.core_netpacket_interface->protocol_version) + { + strlcpy(info_buf.core_version, + networking_driver_st.core_netpacket_interface->protocol_version, + sizeof(info_buf.core_version)); + } + info_buf.content_crc = htonl(content_get_crc()); /* Get our content CRC */ connection->ping_timer = cpu_features_get_time_usec(); /* Third ping */ @@ -1607,19 +1618,30 @@ static bool netplay_handshake_pre_info(netplay_t *netplay, RARCH_ERR("[Netplay] %s\n", dmsg); if (!netplay->is_server) runloop_msg_queue_push(dmsg, 1, 180, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR); return false; } + + const char* my_core_version = system->library_version; + /* When using the netpacket interface, the core can override the version + * with a custom string so multiple core versions can stay compatible. */ + if (networking_driver_st.core_netpacket_interface && + networking_driver_st.core_netpacket_interface->protocol_version) + { + my_core_version = + networking_driver_st.core_netpacket_interface->protocol_version; + } + STRING_SAFE(info_buf.core_version, sizeof(info_buf.core_version)); if (!string_is_equal_case_insensitive( - info_buf.core_version, system->library_version)) + info_buf.core_version, my_core_version)) { const char *dmsg = msg_hash_to_str( MSG_NETPLAY_DIFFERENT_CORE_VERSIONS); RARCH_WARN("[Netplay] %s\n", dmsg); if (!netplay->is_server && extra_notifications) runloop_msg_queue_push(dmsg, 1, 180, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); } } @@ -1628,11 +1650,14 @@ static bool netplay_handshake_pre_info(netplay_t *netplay, if (content_crc && ntohl(info_buf.content_crc) != content_crc) { - const char *dmsg = msg_hash_to_str(MSG_CONTENT_CRC32S_DIFFER); + /* Warning of a different severety when using netpacket interface */ + const char *dmsg = msg_hash_to_str( + netplay->modus == NETPLAY_MODUS_CORE_PACKET_INTERFACE ? + MSG_CONTENT_NETPACKET_CRC32S_DIFFER : MSG_CONTENT_CRC32S_DIFFER); RARCH_WARN("[Netplay] %s\n", dmsg); if (!netplay->is_server && extra_notifications) runloop_msg_queue_push(dmsg, 1, 180, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); } /* Now switch to the right mode */ @@ -1920,8 +1945,9 @@ static bool netplay_handshake_pre_sync(netplay_t *netplay, /* Tell a core that uses the netpacket interface that the client is ready */ if (networking_driver_st.core_netpacket_interface && networking_driver_st.core_netpacket_interface->start) - networking_driver_st.core_netpacket_interface->start - ((uint16_t)netplay->self_client_num, netplay_netpacket_send_cb); + networking_driver_st.core_netpacket_interface->start( + (uint16_t)netplay->self_client_num, + netplay_netpacket_send_cb, netplay_netpacket_poll_receive_cb); /* Ask to switch to playing mode if we should */ if (!settings->bools.netplay_start_as_spectator) @@ -6254,7 +6280,6 @@ static bool netplay_get_cmd(netplay_t *netplay, } case NETPLAY_CMD_NETPACKET: - case NETPLAY_CMD_NETPACKET_BROADCAST: { uint32_t pkt_client_id; void* buf = netplay->zbuffer; @@ -6279,36 +6304,38 @@ static bool netplay_get_cmd(netplay_t *netplay, if (!netplay->is_server) { - /* packets arriving at a client are always meant for us */ + /* packets arriving at a client are always meant for us + * pkt_client_id contains the original sender of the packet */ if (networking_driver_st.core_netpacket_interface->receive) networking_driver_st.core_netpacket_interface->receive (buf, cmd_size, (uint16_t)pkt_client_id); } else { - bool broadcast = (cmd == NETPLAY_CMD_NETPACKET_BROADCAST); + bool broadcast = (pkt_client_id == RETRO_NETPACKET_BROADCAST); uint16_t incoming_client_id = (uint16_t)(connection - netplay->connections + 1); - /* check if this is a packet for the host */ - if ((broadcast ? pkt_client_id : !pkt_client_id) + /* check if this is a packet for the host + * pkt_client_id designates the recipient of the packet */ + if ((broadcast || pkt_client_id == 0) && networking_driver_st.core_netpacket_interface->receive) networking_driver_st.core_netpacket_interface->receive (buf, cmd_size, incoming_client_id); if (broadcast) { - /* relay to all but designated client and incoming client */ - size_t i, skip1 = pkt_client_id, skip2 = incoming_client_id; + /* relay to all but incoming client */ + size_t i, skip = incoming_client_id - 1; for (i = 0; i < netplay->connections_size; i++) - if (i+1 != skip1 && i+1 != skip2) + if (i != skip) netplay_send_cmd_netpacket(netplay, i, - buf, cmd_size, incoming_client_id, false); + buf, cmd_size, incoming_client_id); } /* Relay unless target is the host or the incoming client */ else if (pkt_client_id && pkt_client_id != incoming_client_id) netplay_send_cmd_netpacket(netplay, pkt_client_id-1, - buf, cmd_size, incoming_client_id, false); + buf, cmd_size, incoming_client_id); } break; } @@ -8831,7 +8858,8 @@ bool init_netplay(const char *server, unsigned port, const char *mitm_session) /* Tell a core that uses the netpacket interface that the host is ready */ if (netplay->is_server && net_st->core_netpacket_interface && net_st->core_netpacket_interface->start) - net_st->core_netpacket_interface->start(0, netplay_netpacket_send_cb); + net_st->core_netpacket_interface->start(0, + netplay_netpacket_send_cb, netplay_netpacket_poll_receive_cb); return true; @@ -9428,13 +9456,12 @@ bool netplay_decode_hostname(const char *hostname, * @conn_idx : connection index to send cmd to * @buf : packet data pointer * @len : packet data size - * @pkt_client_id : source id if host sending to client, otherwise recipient id or excepted id if broadcast - * @broadcast : pass true on client if host should relay this to everyone else + * @pkt_client_id : source id if host sending to client, otherwise recipient id or RETRO_NETPACKET_BROADCAST * * Send a netpacket command to a connected peer. */ static void netplay_send_cmd_netpacket(netplay_t *netplay, size_t conn_idx, - const void* buf, size_t len, uint16_t pkt_client_id, bool broadcast) + const void* buf, size_t len, uint16_t pkt_client_id) { struct netplay_connection *connection; struct socket_buffer *sbuf; @@ -9446,8 +9473,7 @@ static void netplay_send_cmd_netpacket(netplay_t *netplay, size_t conn_idx, if (!(connection->flags & NETPLAY_CONN_FLAG_ACTIVE)) return; if (connection->mode != NETPLAY_CONNECTION_PLAYING) return; - cmdbuf[0] = htonl(broadcast - ? NETPLAY_CMD_NETPACKET_BROADCAST : NETPLAY_CMD_NETPACKET); + cmdbuf[0] = htonl(NETPLAY_CMD_NETPACKET); cmdbuf[1] = htonl(len); cmdbuf[2] = htonl(pkt_client_id); @@ -9461,40 +9487,48 @@ static void netplay_send_cmd_netpacket(netplay_t *netplay, size_t conn_idx, } static void RETRO_CALLCONV netplay_netpacket_send_cb(int flags, - const void* buf, size_t len, uint16_t client_id, bool broadcast) + const void* buf, size_t len, uint16_t client_id) { net_driver_state_t *net_st = &networking_driver_st; netplay_t *netplay = net_st->data; if (!netplay) return; - if (buf == NULL) + if (buf && len) { - /* With NULL this function instead flushes packets and checks incoming */ - netplay_send_flush_all(netplay, NULL); - input_poll_net(netplay); - return; + if (!netplay->is_server) + { + /* client always sends packet to host, host will relay it if needed */ + netplay_send_cmd_netpacket(netplay, 0, buf, len, client_id); + } + else if (client_id == RETRO_NETPACKET_BROADCAST) + { + /* send packet to all clients */ + size_t i; + for (i = 0; i < netplay->connections_size; i++) + netplay_send_cmd_netpacket(netplay, i, buf, len, 0); + } + else if (client_id) + { + /* send packet to specific client */ + netplay_send_cmd_netpacket(netplay, client_id-1, buf, len, 0); + } } - if (!netplay->is_server) - { - /* client always sends packet to host, host will relay it if needed */ - netplay_send_cmd_netpacket(netplay, 0, buf, len, client_id, broadcast); - } - else if (broadcast) - { - /* send packet to all clients (client_id can be set as exception) */ - size_t i; - for (i = 0; i < netplay->connections_size; i++) - if (i+1 != client_id) - netplay_send_cmd_netpacket(netplay, i, buf, len, 0, false); - } - else if (client_id) + if (flags & RETRO_NETPACKET_FLUSH_HINT) { - /* send packet to specific client */ - netplay_send_cmd_netpacket(netplay, client_id-1, buf, len, 0, false); + netplay_send_flush_all(netplay, NULL); } } +static void RETRO_CALLCONV netplay_netpacket_poll_receive_cb() +{ + net_driver_state_t *net_st = &networking_driver_st; + netplay_t *netplay = net_st->data; + if (!netplay) return; + + netplay_poll_net_input(netplay); +} + /* Netplay Widgets */ #ifdef HAVE_GFX_WIDGETS diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index e8d3e8bc7f02..40df570613ef 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -165,9 +165,6 @@ enum netplay_cmd /* Send a network packet from the raw packet core interface */ NETPLAY_CMD_NETPACKET = 0x0048, - /* Used by clients to have the host also forward it to other clients */ - NETPLAY_CMD_NETPACKET_BROADCAST = 0x0049, - /* Misc. commands */ /* Sends multiple config requests over, From fe809e64359113afe6bc6a7c8678dd13af1c46d0 Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Fri, 10 Nov 2023 01:19:08 +0900 Subject: [PATCH 2/2] C89 compile fix --- network/netplay/netplay_frontend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 6987ee4f1218..c329eba2282f 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -1609,6 +1609,7 @@ static bool netplay_handshake_pre_info(netplay_t *netplay, /* Check the core info */ if (system) { + const char* my_core_version = system->library_version; STRING_SAFE(info_buf.core_name, sizeof(info_buf.core_name)); if (!string_is_equal_case_insensitive( info_buf.core_name, system->library_name)) @@ -1622,7 +1623,6 @@ static bool netplay_handshake_pre_info(netplay_t *netplay, return false; } - const char* my_core_version = system->library_version; /* When using the netpacket interface, the core can override the version * with a custom string so multiple core versions can stay compatible. */ if (networking_driver_st.core_netpacket_interface &&