diff --git a/libretro-common/include/libretro.h b/libretro-common/include/libretro.h index a08b69c083..8021f749be 100644 --- a/libretro-common/include/libretro.h +++ b/libretro-common/include/libretro.h @@ -1809,6 +1809,28 @@ enum retro_mod * even before the microphone driver is ready. */ +#define RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE 76 + /* const struct retro_netpacket_callback * -- + * When set, a core gets control over network packets sent and + * received during a multiplayer session. This can be used to emulate + * multiplayer games that were originally played on 2 or more separate + * consoles or computers connected together. + * + * The frontend will take care of connecting players together. + * The core only needs to send the actual data as needed for the + * emulation while handshake and connection management happens in + * the background. + * + * When 2 or more players are connected and this interface has been + * set, time manipulation features (pausing, slow motion, fast forward, + * rewinding, save state loading, etc.) are disabled to not interrupt + * communication. + * + * When not set, a frontend may use state serialization based + * multiplayer where a deterministic core supporting multiple + * input devices does not need to do anything on its own. + */ + /* VFS functionality */ /* File paths: @@ -3030,6 +3052,89 @@ struct retro_disk_control_ext_callback retro_get_image_label_t get_image_label; /* Optional - may be NULL */ }; +/* Callbacks for RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE. + * A core can set it if sending and receiving custom network packets + * during a multiplayer session is desired. + */ + +/* Used by the core to send a packet to one or more connected players. + * A single packet sent via this interface can contain up to 64kb of data. + * + * If the ready callback has indicated the local player to be the host: + * - The broadcast flag can be set to true to send to multiple connected clients + * - On a broadcast, the client_id argument indicates 1 client NOT to send the packet to + * - Otherwise, the client_id argument indicates a single client to send the packet to + * If the local player is a client connected to a host: + * - The broadcast flag is ignored + * - The client_id argument must be set to 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)(const void* buf, size_t len, uint16_t client_id, bool broadcast); + +/* 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 + * point no other player has connected yet. + * + * 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 will have to store the retro_netpacket_send_t function pointer + * passed here and use it whenever it wants to send a packet. That send + * function pointer is 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); + +/* Called by the frontend when a new packet arrives which has been sent from + * a connected client or the host with retro_netpacket_send_t. + * The client_id argument indicates who has sent the packet. On the host side + * this will always be > 0 (coming from a connected client). + * On a client connected to the host it is always 0 (coming from the host). + * Packets sent with this interface arrive at this callback in a reliable + * manner, meaning in the same order they were sent and without packet loss. + */ +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. + */ +typedef void (RETRO_CALLCONV *retro_netpacket_stop_t)(void); + +/* Called by the frontend every frame (between calls to retro_run while + * updating the state of the multiplayer session. + * This is a good place for the core to call retro_netpacket_send_t from. + */ +typedef void (RETRO_CALLCONV *retro_netpacket_poll_t)(void); + +/* Called by the frontend when a new player connects to the hosted session. + * This is only called on the host side, not for clients connected to the host. + */ +typedef void (RETRO_CALLCONV *retro_netpacket_connected_t)(uint16_t client_id); + +/* Called by the frontend when a player leaves or disconnects from the hosted session. + * This is only called on the host side, not for clients connected to the host. + */ +typedef void (RETRO_CALLCONV *retro_netpacket_disconnected_t)(uint16_t client_id); + +/** + * A callback interface for giving a core the ability to send and receive custom + * network packets during a multiplayer session between two or more instances + * of a libretro frontend. + * + * @see RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE + */ +struct retro_netpacket_callback +{ + retro_netpacket_start_t start; + retro_netpacket_receive_t receive; + retro_netpacket_stop_t stop; /* Optional - may be NULL */ + 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 */ +}; + enum retro_pixel_format { /* 0RGB1555, native endian. diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 0bd1eb6edb..48e146fe77 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -147,6 +147,7 @@ typedef struct struct netplay_room host_room; struct netplay_room *room_list; struct netplay_rooms *rooms_data; + struct retro_netpacket_callback *core_netpacket_interface; /* Used while Netplay is running */ netplay_t *data; netplay_client_info_t *client_info; diff --git a/network/netplay/netplay_defines.h b/network/netplay/netplay_defines.h index a52021480c..6c8c78340b 100644 --- a/network/netplay/netplay_defines.h +++ b/network/netplay/netplay_defines.h @@ -74,7 +74,10 @@ enum rarch_netplay_ctl_state RARCH_NETPLAY_CTL_DESYNC_PUSH, RARCH_NETPLAY_CTL_DESYNC_POP, RARCH_NETPLAY_CTL_KICK_CLIENT, - RARCH_NETPLAY_CTL_BAN_CLIENT + RARCH_NETPLAY_CTL_BAN_CLIENT, + RARCH_NETPLAY_CTL_SET_CORE_PACKET_INTERFACE, + RARCH_NETPLAY_CTL_SKIP_NETPLAY_CALLBACKS, + RARCH_NETPLAY_CTL_ALLOW_TIMESKIP }; /* The current status of a connection */ diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index b45b64b9a6..daf8209663 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -675,6 +675,9 @@ static uint32_t simple_rand_uint32(unsigned long *simple_rand_next) return ((part0 << 30) + (part1 << 15) + part2); } +static void RETRO_CALLCONV netplay_netpacket_send(const void* buf, size_t len, + uint16_t client_id, bool broadcast); + /* * netplay_init_socket_buffer * @@ -1876,6 +1879,12 @@ static bool netplay_handshake_pre_sync(netplay_t *netplay, netplay->next_ping = cpu_features_get_time_usec() + NETPLAY_PING_AFTER; + /* 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); + /* Ask to switch to playing mode if we should */ if (!settings->bools.netplay_start_as_spectator) return netplay_cmd_mode(netplay, NETPLAY_CONNECTION_PLAYING); @@ -3541,7 +3550,8 @@ static bool netplay_sync_pre_frame(netplay_t *netplay) bool ret = true; if (netplay->run_frame_count > 0 && netplay_delta_frame_ready(netplay, - &netplay->buffer[netplay->run_ptr], netplay->run_frame_count)) + &netplay->buffer[netplay->run_ptr], netplay->run_frame_count) + && !networking_driver_st.core_netpacket_interface) { /* Don't serialize until it's safe. */ if (!(netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)) @@ -3664,6 +3674,10 @@ static void netplay_sync_post_frame(netplay_t *netplay, bool stalled) { uint32_t lo_frame_count, hi_frame_count; + /* When a core uses the netpacket interface frames are not synced */ + if (networking_driver_st.core_netpacket_interface) + return; + /* Unless we're stalling, we've just finished running a frame */ if (!stalled) { @@ -3748,8 +3762,9 @@ static void netplay_sync_post_frame(netplay_t *netplay, bool stalled) #endif /* Now replay the real input if we've gotten ahead of it */ - if (netplay->force_rewind || + if ((netplay->force_rewind || netplay->replay_frame_count < netplay->run_frame_count) + && !networking_driver_st.core_netpacket_interface) { retro_ctx_serialize_info_t serial_info; @@ -4067,6 +4082,12 @@ static void netplay_hangup(netplay_t *netplay, #endif } + if (networking_driver_st.core_netpacket_interface + && was_playing && netplay->is_server + && networking_driver_st.core_netpacket_interface->disconnected) + networking_driver_st.core_netpacket_interface->disconnected + ((uint16_t)(connection - netplay->connections + 1)); + RARCH_LOG("[Netplay] %s\n", dmsg); /* This notification is really only important to the server if the client was playing. * Let it be optional if server and the client wasn't playing. */ @@ -4180,6 +4201,10 @@ static bool send_input_frame(netplay_t *netplay, struct delta_frame *dframe, buffer[2] = htonl(dframe->frame); buffer[3] = htonl(client_num); + /* When a core uses the netpacket interface input is not shared */ + if (networking_driver_st.core_netpacket_interface) + return true; + /* Add the device data */ devices = netplay->client_devices[client_num]; for (device = 0; device < MAX_INPUT_DEVICES; device++) @@ -4749,7 +4774,10 @@ static void handle_play_spectate(netplay_t *netplay, if (settings->bools.netplay_allow_slaves) { - if (settings->bools.netplay_require_slaves) + /* Slave mode unused when core uses netpacket interface */ + if (networking_driver_st.core_netpacket_interface) + slave = false; + else if (settings->bools.netplay_require_slaves) slave = true; else slave = (mode & NETPLAY_CMD_PLAY_BIT_SLAVE) ? @@ -4783,6 +4811,11 @@ static void handle_play_spectate(netplay_t *netplay, announce_play_spectate(netplay, connection->nick, connection->mode, devices, connection->ping); + + if (networking_driver_st.core_netpacket_interface + && networking_driver_st.core_netpacket_interface->connected) + networking_driver_st.core_netpacket_interface->connected + ((uint16_t)(connection - netplay->connections + 1)); } else { @@ -5511,7 +5544,9 @@ static bool netplay_get_cmd(netplay_t *netplay, /* A change to me! */ if (mode & NETPLAY_CMD_MODE_BIT_PLAYING) { - if (frame != netplay->server_frame_count) + /* When a core uses the netpacket interface this is valid */ + if (frame != netplay->server_frame_count + && !networking_driver_st.core_netpacket_interface) { RARCH_ERR("[Netplay] Received mode change out of order.\n"); return netplay_cmd_nak(netplay, connection); @@ -5626,7 +5661,9 @@ static bool netplay_get_cmd(netplay_t *netplay, /* Somebody else is joining or parting */ if (mode & NETPLAY_CMD_MODE_BIT_PLAYING) { - if (frame != netplay->server_frame_count) + /* When a core uses the netpacket interface this is valid */ + if (frame != netplay->server_frame_count + && !networking_driver_st.core_netpacket_interface) { RARCH_ERR("[Netplay] Received mode change out of order.\n"); return netplay_cmd_nak(netplay, connection); @@ -6126,6 +6163,32 @@ static bool netplay_get_cmd(netplay_t *netplay, break; } + case NETPLAY_CMD_NETPACKET: + { + if (!networking_driver_st.core_netpacket_interface) + { + RARCH_ERR("[Netplay] NETPLAY_CMD_NETPACKET while core netpacket interface is not set.\n"); + return netplay_cmd_nak(netplay, connection); + } + if (cmd_size > netplay->zbuffer_size) + { + RARCH_ERR("[Netplay] Received netpacket of unexpected size.\n"); + return netplay_cmd_nak(netplay, connection); + } + + RECV(netplay->zbuffer, cmd_size) + return false; + + if (networking_driver_st.core_netpacket_interface->receive) + { + uint16_t client_id = (!netplay->is_server ? (uint16_t)0 : + (uint16_t)(connection - netplay->connections + 1)); + networking_driver_st.core_netpacket_interface->receive + (netplay->zbuffer, cmd_size, client_id); + } + break; + } + case NETPLAY_CMD_PLAYER_CHAT: { char nickname[NETPLAY_NICK_LEN]; @@ -6742,15 +6805,23 @@ static bool netplay_init_socket_buffers(netplay_t *netplay) static bool netplay_init_serialization(netplay_t *netplay) { - size_t i, info_size; + size_t i; if (netplay->state_size) return true; - info_size = core_serialize_size_special(); - if (!info_size) - return false; - netplay->state_size = info_size; + if (networking_driver_st.core_netpacket_interface) + { + /* max core netpacket size is 64 kb, hold 2 of them */ + netplay->state_size = 64*1024*2; + } + else + { + size_t info_size = core_serialize_size_special(); + if (!info_size) + return false; + netplay->state_size = info_size; + } for (i = 0; i < netplay->buffer_size; i++) { @@ -7127,6 +7198,12 @@ static void netplay_frontend_paused(netplay_t *netplay, bool paused) size_t i; uint32_t paused_ct = 0; + /* When a core uses the netpacket interface netplay doesn't control pause. + * We need this because even if RARCH_NETPLAY_CTL_ALLOW_PAUSE returns false + * on some platforms the frontend may try to force netplay to pause. */ + if (networking_driver_st.core_netpacket_interface) + return; + netplay->local_paused = paused; /* Communicating this is a bit odd: If exactly one other connection is @@ -7250,6 +7327,10 @@ void netplay_load_savestate(netplay_t *netplay, { retro_ctx_serialize_info_t tmp_serial_info = {0}; + /* When a core uses the netpacket interface save states are not shared */ + if (networking_driver_st.core_netpacket_interface) + return; + if (!serial_info) save = true; @@ -7581,6 +7662,18 @@ static bool netplay_poll(netplay_t *netplay, bool block_libretro_input) { size_t i; + /* Use simplified polling loop when a core uses the netpacket interface. */ + if (networking_driver_st.core_netpacket_interface) + { + if (netplay->self_mode == NETPLAY_CONNECTION_NONE) + return true; + netplay_poll_net_input(netplay); + if (networking_driver_st.core_netpacket_interface->poll + && netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + networking_driver_st.core_netpacket_interface->poll(); + return true; + } + if (!get_self_input_state(block_libretro_input, netplay)) goto catastrophe; @@ -8442,6 +8535,10 @@ void deinit_netplay(void) /* Reinitialize preemptive frames if enabled */ preempt_init(runloop_state_get_ptr()); #endif + + if (net_st->core_netpacket_interface + && net_st->core_netpacket_interface->stop) + net_st->core_netpacket_interface->stop(); } free(net_st->client_info); @@ -8471,9 +8568,10 @@ bool init_netplay(const char *server, unsigned port, const char *mitm_session) serialization_quirks = core_serialization_quirks(); - if (!core_info_current_supports_netplay() || + if ((!core_info_current_supports_netplay() || serialization_quirks & (RETRO_SERIALIZATION_QUIRK_INCOMPLETE | RETRO_SERIALIZATION_QUIRK_SINGLE_SESSION)) + && !net_st->core_netpacket_interface) { RARCH_ERR("[Netplay] %s\n", msg_hash_to_str(MSG_NETPLAY_UNSUPPORTED)); runloop_msg_queue_push( @@ -8593,6 +8691,11 @@ bool init_netplay(const char *server, unsigned port, const char *mitm_session) netplay->self_mode = NETPLAY_CONNECTION_INIT; } + /* 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); + return true; failure: @@ -8748,6 +8851,18 @@ static bool kick_client_by_id_and_name(netplay_t *netplay, return true; } +static bool netplay_have_any_active_connection(netplay_t *netplay) +{ + size_t i; + if (!netplay || !netplay->is_server) + return (netplay && netplay->self_mode >= NETPLAY_CONNECTION_CONNECTED); + for (i = 0; i < netplay->connections_size; i++) + if ( (netplay->connections[i].flags & NETPLAY_CONN_FLAG_ACTIVE) + && (netplay->connections[i].mode >= NETPLAY_CONNECTION_CONNECTED)) + return true; + return false; +} + /** * netplay_driver_ctl * @@ -8915,7 +9030,7 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) break; case RARCH_NETPLAY_CTL_GAME_WATCH: - if (netplay) + if (netplay && net_st->core_netpacket_interface == NULL) netplay_toggle_play_spectate(netplay); else ret = false; @@ -8929,7 +9044,9 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) break; case RARCH_NETPLAY_CTL_ALLOW_PAUSE: - ret = (!netplay || netplay->allow_pausing); + ret = (!netplay || netplay->allow_pausing) && + ((net_st->core_netpacket_interface == NULL) + || !netplay_have_any_active_connection(netplay)); break; case RARCH_NETPLAY_CTL_PAUSE: @@ -8944,13 +9061,13 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) break; case RARCH_NETPLAY_CTL_LOAD_SAVESTATE: - if (netplay) + if (netplay && !net_st->core_netpacket_interface) netplay_load_savestate(netplay, (retro_ctx_serialize_info_t*)data, true); break; case RARCH_NETPLAY_CTL_RESET: - if (netplay) + if (netplay && !net_st->core_netpacket_interface) netplay_core_reset(netplay); break; @@ -8967,7 +9084,10 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) break; case RARCH_NETPLAY_CTL_DESYNC_PUSH: - if (netplay) + if (net_st->core_netpacket_interface + && netplay_have_any_active_connection(netplay)) + ret = false; + else if (netplay) netplay->desync++; break; @@ -9027,6 +9147,35 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) ret = false; break; + case RARCH_NETPLAY_CTL_SET_CORE_PACKET_INTERFACE: + if (net_st->core_netpacket_interface) + { + free(net_st->core_netpacket_interface); + net_st->core_netpacket_interface = NULL; + } + + if (data) + { + /* copy passed interface to local state */ + net_st->core_netpacket_interface = (struct retro_netpacket_callback*) + malloc(sizeof(*net_st->core_netpacket_interface)); + *net_st->core_netpacket_interface = *(struct retro_netpacket_callback*)data; + /* reset savefile dir as core_netpacket_interface affects it */ + runloop_path_set_redirect(config_get_ptr(), + dir_get_ptr(RARCH_DIR_SAVEFILE), + dir_get_ptr(RARCH_DIR_CURRENT_SAVESTATE)); + } + break; + + case RARCH_NETPLAY_CTL_SKIP_NETPLAY_CALLBACKS: + ret = (net_st->core_netpacket_interface != NULL); + break; + + case RARCH_NETPLAY_CTL_ALLOW_TIMESKIP: + ret = ((net_st->core_netpacket_interface == NULL) + || !netplay_have_any_active_connection(netplay)); + break; + case RARCH_NETPLAY_CTL_NONE: default: ret = false; @@ -9122,6 +9271,44 @@ bool netplay_decode_hostname(const char *hostname, return true; } +static void RETRO_CALLCONV netplay_netpacket_send(const void* buf, size_t len, + uint16_t client_id, bool broadcast) +{ + net_driver_state_t *net_st = &networking_driver_st; + netplay_t *netplay = net_st->data; + if (!netplay) return; + + if (broadcast && netplay->is_server) + { + size_t i, skip = client_id; + for (i = 0; i < netplay->connections_size; i++) + { + struct netplay_connection *connection = &netplay->connections[i]; + if (i+1 != skip && (connection->flags & NETPLAY_CONN_FLAG_ACTIVE) + && (connection->mode == NETPLAY_CONNECTION_PLAYING) + && !netplay_send_raw_cmd(netplay, connection, + NETPLAY_CMD_NETPACKET, buf, len)) + netplay_hangup(netplay, connection); + } + } + else + { + size_t i = client_id - (netplay->is_server ? 1 : 0); + if (i >= netplay->connections_size) + { + RARCH_ERR("[Netplay] Unable to send netpacket to client id %d.\n", + client_id); + return; + } + struct netplay_connection *connection = &netplay->connections[i]; + if ( (connection->flags & NETPLAY_CONN_FLAG_ACTIVE) + && (connection->mode == NETPLAY_CONNECTION_PLAYING) + && !netplay_send_raw_cmd(netplay, connection, + NETPLAY_CMD_NETPACKET, buf, len)) + netplay_hangup(netplay, connection); + } +} + /* Netplay Widgets */ #ifdef HAVE_GFX_WIDGETS diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 781d30bbab..05257b77bf 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -162,6 +162,9 @@ enum netplay_cmd /* Sends over cheats enabled on client (unsupported) */ NETPLAY_CMD_CHEATS = 0x0047, + /* Send a network packet from the raw packet core interface */ + NETPLAY_CMD_NETPACKET = 0x0048, + /* Misc. commands */ /* Sends multiple config requests over, diff --git a/retroarch.c b/retroarch.c index 9e9de2bad9..723d9ff856 100644 --- a/retroarch.c +++ b/retroarch.c @@ -2528,6 +2528,10 @@ bool command_event(enum event_command cmd, void *data) runloop_msg_queue_push(msg_hash_to_str(MSG_CHEEVOS_LOAD_STATE_PREVENTED_BY_HARDCORE_MODE), 0, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); return false; } +#endif +#ifdef HAVE_NETWORKING + if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_TIMESKIP, NULL)) + return false; #endif if (!command_event_main_state(cmd)) return false; diff --git a/runloop.c b/runloop.c index 4603c60cc8..62813f6753 100644 --- a/runloop.c +++ b/runloop.c @@ -3484,6 +3484,13 @@ bool runloop_environment_cb(unsigned cmd, void *data) } break; + case RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE: +#ifdef HAVE_NETWORKING + RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE.\n"); + netplay_driver_ctl(RARCH_NETPLAY_CTL_SET_CORE_PACKET_INTERFACE, data); +#endif + break; + default: RARCH_LOG("[Environ]: UNSUPPORTED (#%u).\n", cmd); return false; @@ -4032,6 +4039,9 @@ void runloop_event_deinit_core(void) #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) runloop_st->runtime_shader_preset_path[0] = '\0'; #endif +#ifdef HAVE_NETWORKING + netplay_driver_ctl(RARCH_NETPLAY_CTL_SET_CORE_PACKET_INTERFACE, NULL); +#endif } static bool runloop_path_init_subsystem(runloop_state_t *runloop_st) @@ -6310,6 +6320,12 @@ static enum runloop_state_enum runloop_check_state( input_st->flags |= INP_FLAG_NONBLOCKING; } +#ifdef HAVE_NETWORKING + if (check2 + && !netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_TIMESKIP, NULL)) + check2 = false; +#endif + if (check2) { if (input_st->flags & INP_FLAG_NONBLOCKING) @@ -6408,6 +6424,14 @@ static enum runloop_state_enum runloop_check_state( runloop_st->flags &= ~RUNLOOP_FLAG_SLOWMOTION; } +#ifdef HAVE_NETWORKING + if ((runloop_st->flags & RUNLOOP_FLAG_SLOWMOTION) + && !netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_TIMESKIP, NULL)) + { + runloop_st->flags &= ~RUNLOOP_FLAG_SLOWMOTION; + } +#endif + if (runloop_st->flags & RUNLOOP_FLAG_SLOWMOTION) { if (settings->uints.video_black_frame_insertion) @@ -7404,6 +7428,9 @@ bool core_set_netplay_callbacks(void) { runloop_state_t *runloop_st = &runloop_state; + if (netplay_driver_ctl(RARCH_NETPLAY_CTL_SKIP_NETPLAY_CALLBACKS, NULL)) + return true; + /* Force normal poll type for netplay. */ runloop_st->current_core.poll_type = POLL_TYPE_NORMAL; @@ -7983,7 +8010,8 @@ void runloop_path_set_redirect(settings_t *settings, #ifdef HAVE_NETWORKING /* Special save directory for netplay clients. */ if ( netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL) - && !netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_SERVER, NULL)) + && !netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_SERVER, NULL) + && !netplay_driver_ctl(RARCH_NETPLAY_CTL_SKIP_NETPLAY_CALLBACKS, NULL)) { fill_pathname_join(new_savefile_dir, new_savefile_dir, ".netplay", sizeof(new_savefile_dir)); diff --git a/state_manager.c b/state_manager.c index 238517cb43..98ea01bbc5 100644 --- a/state_manager.c +++ b/state_manager.c @@ -748,8 +748,9 @@ bool state_manager_check_rewind( { #ifdef HAVE_NETWORKING /* Make sure netplay isn't confused */ - if (!was_reversed) - netplay_driver_ctl(RARCH_NETPLAY_CTL_DESYNC_PUSH, NULL); + if (!was_reversed + && !netplay_driver_ctl(RARCH_NETPLAY_CTL_DESYNC_PUSH, NULL)) + return false; #endif rewind_st->flags |= STATE_MGR_REWIND_ST_FLAG_FRAME_IS_REVERSED; diff --git a/tasks/task_netplay_find_content.c b/tasks/task_netplay_find_content.c index 0dc84b94d7..08cfee0f15 100644 --- a/tasks/task_netplay_find_content.c +++ b/tasks/task_netplay_find_content.c @@ -894,9 +894,7 @@ bool task_push_netplay_content_reload(const char *hostname) strlcpy(data->hostname, hostname, sizeof(data->hostname)); flags = content_get_flags(); - if (flags & CONTENT_ST_FLAG_CORE_DOES_NOT_NEED_CONTENT) - scan_state.state |= STATE_LOAD_CONTENTLESS; - else if (flags & CONTENT_ST_FLAG_IS_INITED) + if (flags & CONTENT_ST_FLAG_IS_INITED) { const char *psubsystem = path_get(RARCH_PATH_SUBSYSTEM); @@ -929,6 +927,10 @@ bool task_push_netplay_content_reload(const char *hostname) } } + if ((flags & CONTENT_ST_FLAG_CORE_DOES_NOT_NEED_CONTENT) && + !(scan_state.state & (STATE_LOAD|STATE_LOAD_SUBSYSTEM))) + scan_state.state |= STATE_LOAD_CONTENTLESS; + data->current.core_loaded = true; scan_state.running = true;