Merge pull request #14054 from Cthulhu-throwaway/netplay-refactor

(Netplay) Some refactoring and fixes
This commit is contained in:
LibretroAdmin 2022-06-18 03:28:47 +01:00 committed by GitHub
commit 838e5117d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1352 additions and 1607 deletions

View File

@ -5959,113 +5959,109 @@ static int action_ok_wifi_disconnect(const char *path,
} }
#endif #endif
static int action_ok_netplay_connect_room(const char *path, static int action_ok_netplay_connect_room(const char *path, const char *label,
const char *label, unsigned type, size_t idx, size_t entry_idx) unsigned type, size_t idx, size_t entry_idx)
{ {
char tmp_hostname[4115]; char hostname[512];
struct netplay_room *room;
net_driver_state_t *net_st = networking_state_get_ptr(); net_driver_state_t *net_st = networking_state_get_ptr();
unsigned room_index = type - MENU_SETTINGS_NETPLAY_ROOMS_START; unsigned room_index = type - MENU_SETTINGS_NETPLAY_ROOMS_START;
if (room_index >= (unsigned)net_st->room_count) if (room_index >= (unsigned)net_st->room_count)
return menu_cbs_exit(); return menu_cbs_exit();
tmp_hostname[0] = '\0'; room = &net_st->room_list[room_index];
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL)) if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL))
generic_action_ok_command(CMD_EVENT_NETPLAY_DEINIT); generic_action_ok_command(CMD_EVENT_NETPLAY_DEINIT);
netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL); netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL);
if (net_st->room_list[room_index].host_method == NETPLAY_HOST_METHOD_MITM) if (room->host_method == NETPLAY_HOST_METHOD_MITM)
snprintf(tmp_hostname, snprintf(hostname, sizeof(hostname), "%s|%d|%s",
sizeof(tmp_hostname), room->mitm_address, room->mitm_port, room->mitm_session);
"%s|%d|%s",
net_st->room_list[room_index].mitm_address,
net_st->room_list[room_index].mitm_port,
net_st->room_list[room_index].mitm_session);
else else
snprintf(tmp_hostname, snprintf(hostname, sizeof(hostname), "%s|%d", room->address, room->port);
sizeof(tmp_hostname),
"%s|%d",
net_st->room_list[room_index].address,
net_st->room_list[room_index].port);
#if 0 #if 0
RARCH_LOG("[lobby] connecting to: %s with game: %s/%08x\n", RARCH_LOG("[Lobby] Connecting to: %s with game: %s/%08x\n",
tmp_hostname, hostname, room->gamename, room->gamecrc);
net_st->room_list[room_index].gamename,
net_st->room_list[room_index].gamecrc);
#endif #endif
task_push_netplay_crc_scan( task_push_netplay_crc_scan(room->gamecrc, room->gamename, hostname,
net_st->room_list[room_index].gamecrc, room->corename, room->subsystem_name);
net_st->room_list[room_index].gamename,
tmp_hostname,
net_st->room_list[room_index].corename,
net_st->room_list[room_index].subsystem_name);
return 0; return 0;
} }
static void netplay_refresh_rooms_cb(retro_task_t *task, static void netplay_refresh_rooms_cb(retro_task_t *task, void *task_data,
void *task_data, void *user_data, const char *error) void *user_data, const char *error)
{ {
char *new_data = NULL; char *room_data = NULL;
const char *path = NULL; const char *path = NULL;
const char *label = NULL; const char *label = NULL;
unsigned menu_type = 0; unsigned menu_type = 0;
enum msg_hash_enums enum_idx = MSG_UNKNOWN; enum msg_hash_enums enum_idx = MSG_UNKNOWN;
net_driver_state_t *net_st = networking_state_get_ptr();
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
bool refresh = false; bool refresh = false;
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
net_driver_state_t *net_st = networking_state_get_ptr();
free(net_st->room_list);
net_st->room_list = NULL;
net_st->room_count = 0;
menu_entries_get_last_stack(&path, &label, &menu_type, &enum_idx, NULL); menu_entries_get_last_stack(&path, &label, &menu_type, &enum_idx, NULL);
/* Don't push the results if we left the netplay menu */ /* Don't push the results if we left the netplay menu */
if (!string_is_equal(label, if (!string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_TAB)) &&
msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_TAB)) && !string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY)))
!string_is_equal(label,
msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY)))
return; return;
if (error) if (error)
{ {
RARCH_ERR("%s: %s\n", msg_hash_to_str(MSG_DOWNLOAD_FAILED), RARCH_ERR("%s: %s\n", msg_hash_to_str(MSG_DOWNLOAD_FAILED), error);
error); goto done;
return;
} }
if (!data || !data->data || !data->len || data->status != 200) if (!data || !data->data || !data->len || data->status != 200)
{ {
RARCH_ERR("%s\n", msg_hash_to_str(MSG_DOWNLOAD_FAILED)); RARCH_ERR("%s\n", msg_hash_to_str(MSG_DOWNLOAD_FAILED));
return; goto done;
} }
new_data = (char*)realloc(data->data, data->len + 1); room_data = (char*)malloc(data->len + 1);
if (!new_data) if (!room_data)
return; goto done;
data->data = new_data; memcpy(room_data, data->data, data->len);
data->data[data->len] = '\0'; room_data[data->len] = '\0';
if (net_st->room_list) if (!string_is_empty(room_data))
free(net_st->room_list);
net_st->room_list = NULL;
net_st->room_count = 0;
if (!string_is_empty(data->data))
{ {
int i; int room_count;
netplay_rooms_parse(data->data); netplay_rooms_parse(room_data);
net_st->room_count = netplay_rooms_get_count(); room_count = netplay_rooms_get_count();
net_st->room_list = (struct netplay_room*)calloc(net_st->room_count, if (room_count > 0)
sizeof(*net_st->room_list)); {
net_st->room_list = (struct netplay_room*)calloc(room_count,
for (i = 0; i < net_st->room_count; i++)
memcpy(&net_st->room_list[i], netplay_room_get(i),
sizeof(*net_st->room_list)); sizeof(*net_st->room_list));
if (net_st->room_list)
{
int i;
net_st->room_count = room_count;
for (i = 0; i < room_count; i++)
memcpy(&net_st->room_list[i], netplay_room_get(i),
sizeof(*net_st->room_list));
}
}
netplay_rooms_free();
} }
free(room_data);
done:
menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh); menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh);
menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL); menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL);
} }
@ -6073,92 +6069,81 @@ static void netplay_refresh_rooms_cb(retro_task_t *task,
static int action_ok_push_netplay_refresh_rooms(const char *path, static int action_ok_push_netplay_refresh_rooms(const char *path,
const char *label, unsigned type, size_t idx, size_t entry_idx) const char *label, unsigned type, size_t idx, size_t entry_idx)
{ {
#ifndef NETPLAY_TEST_BUILD task_push_http_transfer(FILE_PATH_LOBBY_LIBRETRO_URL "list", true, NULL,
const char *url = "http://lobby.libretro.com/list"; netplay_refresh_rooms_cb, NULL);
#else
const char *url = "http://lobbytest.libretro.com/list";
#endif
task_push_http_transfer(url, true, NULL, netplay_refresh_rooms_cb, NULL);
return 0; return 0;
} }
#ifdef HAVE_NETPLAYDISCOVERY #ifdef HAVE_NETPLAYDISCOVERY
static void netplay_refresh_lan_cb(retro_task_t *task, static void netplay_refresh_lan_cb(retro_task_t *task, void *task_data,
void *task_data, void *user_data, const char *error) void *user_data, const char *error)
{ {
int i;
const char *path = NULL; const char *path = NULL;
const char *label = NULL; const char *label = NULL;
unsigned menu_type = 0; unsigned menu_type = 0;
enum msg_hash_enums enum_idx = MSG_UNKNOWN; enum msg_hash_enums enum_idx = MSG_UNKNOWN;
net_driver_state_t *net_st = networking_state_get_ptr();
struct netplay_host_list *hosts = NULL; struct netplay_host_list *hosts = NULL;
bool refresh = false; bool refresh = false;
net_driver_state_t *net_st = networking_state_get_ptr();
free(net_st->room_list);
net_st->room_list = NULL;
net_st->room_count = 0;
menu_entries_get_last_stack(&path, &label, &menu_type, &enum_idx, NULL); menu_entries_get_last_stack(&path, &label, &menu_type, &enum_idx, NULL);
/* Don't push the results if we left the netplay menu */ /* Don't push the results if we left the netplay menu */
if (!string_is_equal(label, if (!string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_TAB)) &&
msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_TAB)) && !string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY)))
!string_is_equal(label, goto deinit;
msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY)))
goto finished;
if (!netplay_discovery_driver_ctl( if (!netplay_discovery_driver_ctl(
RARCH_NETPLAY_DISCOVERY_CTL_LAN_GET_RESPONSES, &hosts) || RARCH_NETPLAY_DISCOVERY_CTL_LAN_GET_RESPONSES, &hosts))
!hosts) goto done;
goto finished; if (!hosts || !hosts->size)
goto done;
if (net_st->room_list) net_st->room_list =
free(net_st->room_list); (struct netplay_room*)calloc(hosts->size, sizeof(*net_st->room_list));
if (!net_st->room_list)
goto done;
net_st->room_count = hosts->size;
net_st->room_list = NULL; for (i = 0; i < net_st->room_count; i++)
net_st->room_count = 0;
if (hosts->size)
{ {
int i; struct netplay_host *host = &hosts->hosts[i];
struct netplay_room *room = &net_st->room_list[i];
net_st->room_count = hosts->size; room->gamecrc = host->content_crc;
net_st->room_list = (struct netplay_room*)calloc(net_st->room_count, room->port = host->port;
sizeof(*net_st->room_list));
for (i = 0; i < net_st->room_count; i++) strlcpy(room->nickname, host->nick, sizeof(room->nickname));
{ strlcpy(room->frontend, host->frontend, sizeof(room->frontend));
struct netplay_host *host = &hosts->hosts[i]; strlcpy(room->corename, host->core, sizeof(room->corename));
struct netplay_room *room = &net_st->room_list[i]; strlcpy(room->coreversion, host->core_version,
sizeof(room->coreversion));
strlcpy(room->retroarch_version, host->retroarch_version,
sizeof(room->retroarch_version));
strlcpy(room->gamename, host->content, sizeof(room->gamename));
strlcpy(room->subsystem_name, host->subsystem_name,
sizeof(room->subsystem_name));
strlcpy(room->address, host->address, sizeof(room->address));
room->port = host->port; room->has_password = host->has_password;
room->gamecrc = host->content_crc; room->has_spectate_password = host->has_spectate_password;
strlcpy(room->retroarch_version, host->retroarch_version,
sizeof(room->retroarch_version)); room->connectable = true;
strlcpy(room->nickname, host->nick, room->is_retroarch = true;
sizeof(room->nickname)); room->lan = true;
strlcpy(room->subsystem_name, host->subsystem_name,
sizeof(room->subsystem_name));
strlcpy(room->corename, host->core,
sizeof(room->corename));
strlcpy(room->frontend, host->frontend,
sizeof(room->frontend));
strlcpy(room->coreversion, host->core_version,
sizeof(room->coreversion));
strlcpy(room->gamename, host->content,
sizeof(room->gamename));
strlcpy(room->address, host->address,
sizeof(room->address));
room->has_password = host->has_password;
room->has_spectate_password = host->has_spectate_password;
room->connectable = true;
room->is_retroarch = true;
room->lan = true;
}
} }
done:
menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh); menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh);
menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL); menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL);
finished: deinit:
deinit_netplay_discovery(); deinit_netplay_discovery();
} }

View File

@ -1494,93 +1494,48 @@ static int action_bind_sublabel_cheat_desc(
#endif #endif
#ifdef HAVE_NETWORKING #ifdef HAVE_NETWORKING
static int action_bind_sublabel_netplay_room( static int action_bind_sublabel_netplay_room(file_list_t *list,
file_list_t *list,
unsigned type, unsigned i, unsigned type, unsigned i,
const char *label, const char *path, const char *label, const char *path,
char *s, size_t len) char *s, size_t len)
{ {
uint32_t gamecrc = 0; char buf[512];
const char *ra_version = NULL; struct netplay_room *room;
const char *corename = NULL;
const char *gamename = NULL;
const char *core_ver = NULL;
const char *frontend = NULL;
const char *na = NULL;
const char *subsystem = NULL;
net_driver_state_t *net_st = networking_state_get_ptr(); net_driver_state_t *net_st = networking_state_get_ptr();
unsigned room_index = type - MENU_SETTINGS_NETPLAY_ROOMS_START; unsigned room_index = type - MENU_SETTINGS_NETPLAY_ROOMS_START;
if (room_index >= (unsigned)net_st->room_count) if (room_index >= (unsigned)net_st->room_count)
return menu_cbs_exit(); return menu_cbs_exit();
ra_version = net_st->room_list[room_index].retroarch_version; room = &net_st->room_list[room_index];
corename = net_st->room_list[room_index].corename;
gamename = net_st->room_list[room_index].gamename;
core_ver = net_st->room_list[room_index].coreversion;
gamecrc = net_st->room_list[room_index].gamecrc;
frontend = net_st->room_list[room_index].frontend;
subsystem = net_st->room_list[room_index].subsystem_name;
na = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE);
if (string_is_empty(subsystem) || string_is_equal(subsystem, snprintf(s, len,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE))) "%s: %s (%s)\n"
{ "%s: %s (%s)\n"
snprintf(s, len, "%s: %s ",
"%s: %s (%s)\n%s: %s (%s)\nGame: %s (%08lx)", msg_hash_to_str(MSG_PROGRAM),
msg_hash_to_str(MSG_PROGRAM), !string_is_empty(room->retroarch_version) ? room->retroarch_version :
string_is_empty(ra_version) ? na : ra_version, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE),
string_is_empty(frontend) ? na : frontend, (!string_is_empty(room->frontend) &&
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CONTENT_INFO_CORE_NAME), !string_is_equal_case_insensitive(room->frontend, "N/A")) ?
corename, core_ver, room->frontend :
!string_is_equal(gamename, na) ? gamename : na, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE),
(unsigned long)gamecrc); msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CONTENT_INFO_CORE_NAME),
} room->corename, room->coreversion,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CONTENT),
(!string_is_empty(room->gamename) &&
!string_is_equal_case_insensitive(room->gamename, "N/A")) ?
room->gamename :
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE));
if (string_is_empty(room->subsystem_name) ||
string_is_equal_case_insensitive(room->subsystem_name, "N/A"))
snprintf(buf, sizeof(buf), "(%08lX)", (unsigned long)room->gamecrc);
else else
{ snprintf(buf, sizeof(buf), "(%s)", room->subsystem_name);
if (strstr(gamename, "|"))
{
char buf[4096];
unsigned i = 0;
struct string_list list = {0};
string_list_initialize(&list); strlcat(s, buf, len);
string_split_noalloc(&list, gamename, "|");
buf[0] = '\0';
for (i = 0; i < list.size; i++)
{
strlcat(buf, " ", sizeof(buf));
strlcat(buf, list.elems[i].data, sizeof(buf));
/* Never terminate a UI string with a newline */
if (i != list.size - 1)
strlcat(buf, "\n", sizeof(buf));
}
snprintf(s, len,
"%s: %s (%s)\n%s: %s (%s)\nSubsystem: %s\nGames:\n%s",
msg_hash_to_str(MSG_PROGRAM),
string_is_empty(ra_version) ? na : ra_version,
string_is_empty(frontend) ? na : frontend,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CONTENT_INFO_CORE_NAME),
corename, core_ver, subsystem,
!string_is_equal(gamename, na) ? buf : na
);
string_list_deinitialize(&list);
}
else
{
snprintf(s, len,
"%s: %s (%s)\n%s: %s (%s)\nSubsystem: %s\nGame: %s (%08lx)",
msg_hash_to_str(MSG_PROGRAM),
string_is_empty(ra_version) ? na : ra_version,
string_is_empty(frontend) ? na : frontend,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CONTENT_INFO_CORE_NAME),
corename, core_ver, subsystem,
!string_is_equal(gamename, na) ? gamename : na,
(unsigned long)gamecrc);
}
}
return 0; return 0;
} }
@ -1590,10 +1545,10 @@ static int action_bind_sublabel_netplay_kick_client(file_list_t *list,
char *s, size_t len) char *s, size_t len)
{ {
char buf[256]; char buf[256];
netplay_client_info_t *client = NULL; netplay_client_info_t *client;
const char *status = NULL; const char *status = NULL;
size_t idx = list->list[i].entry_idx; size_t idx = list->list[i].entry_idx;
net_driver_state_t *net_st = networking_state_get_ptr(); net_driver_state_t *net_st = networking_state_get_ptr();
if (idx >= net_st->client_info_count) if (idx >= net_st->client_info_count)
return menu_cbs_exit(); return menu_cbs_exit();
@ -1625,20 +1580,17 @@ static int action_bind_sublabel_netplay_kick_client(file_list_t *list,
snprintf(buf, sizeof(buf), "%s: %s", snprintf(buf, sizeof(buf), "%s: %s",
msg_hash_to_str(MSG_NETPLAY_CHAT_SUPPORTED), msg_hash_to_str(MSG_NETPLAY_CHAT_SUPPORTED),
msg_hash_to_str((client->protocol >= 6) ? msg_hash_to_str((client->protocol >= 6) ?
MENU_ENUM_LABEL_VALUE_YES : MENU_ENUM_LABEL_VALUE_YES : MENU_ENUM_LABEL_VALUE_NO));
MENU_ENUM_LABEL_VALUE_NO));
strlcat(s, buf, len); strlcat(s, buf, len);
if (client->ping >= 0) if (client->ping >= 0)
{ {
snprintf(buf, sizeof(buf), "\nPing: %u", snprintf(buf, sizeof(buf), "\nPing: %u", (unsigned)client->ping);
(unsigned)client->ping);
strlcat(s, buf, len); strlcat(s, buf, len);
} }
return 0; return 0;
} }
#endif #endif
static int action_bind_sublabel_playlist_entry( static int action_bind_sublabel_playlist_entry(

View File

@ -10386,9 +10386,16 @@ static unsigned menu_displaylist_build_shader_parameter(
unsigned menu_displaylist_netplay_refresh_rooms(file_list_t *list) unsigned menu_displaylist_netplay_refresh_rooms(file_list_t *list)
{ {
int i; int i;
char buf[256];
char passworded[64];
char country[8];
const char *room_type;
struct netplay_room *room;
unsigned count = 0; unsigned count = 0;
settings_t *settings = config_get_ptr(); settings_t *settings = config_get_ptr();
net_driver_state_t *net_st = networking_state_get_ptr(); net_driver_state_t *net_st = networking_state_get_ptr();
bool show_only_connectable = settings->bools.netplay_show_only_connectable;
bool show_passworded = settings->bools.netplay_show_passworded;
menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, list); menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, list);
@ -10400,8 +10407,8 @@ unsigned menu_displaylist_netplay_refresh_rooms(file_list_t *list)
count++; count++;
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL) && 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_IS_CONNECTED, NULL)) netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_CONNECTED, NULL))
{ {
if (menu_entries_append_enum(list, if (menu_entries_append_enum(list,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_DISCONNECT), msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_DISCONNECT),
@ -10412,10 +10419,10 @@ unsigned menu_displaylist_netplay_refresh_rooms(file_list_t *list)
} }
if (menu_entries_append_enum(list, if (menu_entries_append_enum(list,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_ENABLE_CLIENT), msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_ENABLE_CLIENT),
msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_ENABLE_CLIENT), msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_ENABLE_CLIENT),
MENU_ENUM_LABEL_NETPLAY_ENABLE_CLIENT, MENU_ENUM_LABEL_NETPLAY_ENABLE_CLIENT,
MENU_SETTING_ACTION, 0, 0)) MENU_SETTING_ACTION, 0, 0))
count++; count++;
if (menu_entries_append_enum(list, if (menu_entries_append_enum(list,
@ -10450,11 +10457,7 @@ unsigned menu_displaylist_netplay_refresh_rooms(file_list_t *list)
for (i = 0; i < net_st->room_count; i++) for (i = 0; i < net_st->room_count; i++)
{ {
char buf[8192]; room = &net_st->room_list[i];
char passworded[64];
char country[8];
const char *room_type;
struct netplay_room *room = &net_st->room_list[i];
/* Get rid of any room that is not running RetroArch. */ /* Get rid of any room that is not running RetroArch. */
if (!room->is_retroarch) if (!room->is_retroarch)
@ -10464,7 +10467,7 @@ unsigned menu_displaylist_netplay_refresh_rooms(file_list_t *list)
if the user opt-in. */ if the user opt-in. */
if (!room->connectable) if (!room->connectable)
{ {
if (settings->bools.netplay_show_only_connectable) if (show_only_connectable)
continue; continue;
room_type = msg_hash_to_str(MSG_INTERNET_NOT_CONNECTABLE); room_type = msg_hash_to_str(MSG_INTERNET_NOT_CONNECTABLE);
@ -10480,7 +10483,7 @@ unsigned menu_displaylist_netplay_refresh_rooms(file_list_t *list)
if the user opt-in. */ if the user opt-in. */
if (room->has_password || room->has_spectate_password) if (room->has_password || room->has_spectate_password)
{ {
if (!settings->bools.netplay_show_passworded) if (!show_passworded)
continue; continue;
snprintf(passworded, sizeof(passworded), "[%s] ", snprintf(passworded, sizeof(passworded), "[%s] ",
@ -10490,46 +10493,20 @@ unsigned menu_displaylist_netplay_refresh_rooms(file_list_t *list)
*passworded = '\0'; *passworded = '\0';
if (!room->lan && !string_is_empty(room->country)) if (!room->lan && !string_is_empty(room->country))
snprintf(country, sizeof(country), " (%s)", snprintf(country, sizeof(country), " (%s)", room->country);
room->country);
else else
*country = '\0'; *country = '\0';
snprintf(buf, sizeof(buf), "%s%s: %s%s", snprintf(buf, sizeof(buf), "%s%s: %s%s",
passworded, room_type, passworded, room_type, room->nickname, country);
room->nickname, country);
if (menu_entries_append_enum(list, if (menu_entries_append_enum(list, buf,
buf,
msg_hash_to_str(MENU_ENUM_LABEL_CONNECT_NETPLAY_ROOM), msg_hash_to_str(MENU_ENUM_LABEL_CONNECT_NETPLAY_ROOM),
MENU_ENUM_LABEL_CONNECT_NETPLAY_ROOM, MENU_ENUM_LABEL_CONNECT_NETPLAY_ROOM,
(unsigned)(MENU_SETTINGS_NETPLAY_ROOMS_START + i), 0, 0)) (unsigned)MENU_SETTINGS_NETPLAY_ROOMS_START + i, 0, 0))
count++; count++;
/* Uncomment this to debug mismatched room parameters*/
#if 0
RARCH_LOG("[Lobby]: Room Data: %d\n"
"Nickname: %s\n"
"Address: %s\n"
"Port: %d\n"
"Core: %s\n"
"Core Version: %s\n"
"Game: %s\n"
"Game CRC: %08x\n"
"Timestamp: %d\n", room_data->elems[j + 6].data,
room->nickname,
room->address,
room->port,
room->corename,
room->coreversion,
room->gamename,
room->gamecrc,
room->timestamp);
#endif
} }
netplay_rooms_free();
return count; return count;
} }
@ -10637,13 +10614,14 @@ static unsigned menu_displaylist_netplay_kick(file_list_t *list)
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_REFRESH_CLIENT_INFO, NULL)) if (netplay_driver_ctl(RARCH_NETPLAY_CTL_REFRESH_CLIENT_INFO, NULL))
{ {
char client_id[4];
size_t i; size_t i;
char client_id[4];
netplay_client_info_t *client;
net_driver_state_t *net_st = networking_state_get_ptr(); net_driver_state_t *net_st = networking_state_get_ptr();
for (i = 0; i < net_st->client_info_count; i++) for (i = 0; i < net_st->client_info_count; i++)
{ {
netplay_client_info_t *client = &net_st->client_info[i]; client = &net_st->client_info[i];
snprintf(client_id, sizeof(client_id), "%d", client->id); snprintf(client_id, sizeof(client_id), "%d", client->id);
if (menu_entries_append_enum(list, client->name, client_id, if (menu_entries_append_enum(list, client->name, client_id,
@ -10664,7 +10642,6 @@ static unsigned menu_displaylist_netplay_kick(file_list_t *list)
return count; return count;
} }
#endif #endif
bool menu_displaylist_has_subsystems(void) bool menu_displaylist_has_subsystems(void)

View File

@ -53,7 +53,7 @@ extern "C"
} }
#endif #endif
static discord_state_t discord_state_st; /* int64_t alignment */ static discord_state_t discord_state_st = {0}; /* int64_t alignment */
discord_state_t *discord_state_get_ptr(void) discord_state_t *discord_state_get_ptr(void)
{ {
@ -153,99 +153,113 @@ static void handle_discord_error(int errcode, const char* message)
{ {
} }
static void handle_discord_join_cb(retro_task_t *task, static void handle_discord_join_cb(retro_task_t *task, void *task_data,
void *task_data, void *user_data, const char *err) void *user_data, const char *error)
{ {
char join_hostname[PATH_MAX_LENGTH]; char hostname[512];
struct netplay_room *room = NULL; struct netplay_room *room;
http_transfer_data_t *data = (http_transfer_data_t*)task_data; char *room_data = NULL;
discord_state_t *discord_st = &discord_state_st; http_transfer_data_t *data = (http_transfer_data_t*)task_data;
discord_state_t *discord_st = &discord_state_st;
if (!data || err || !data->data || !data->len) if (error)
goto finish; goto done;
if (!data || !data->data || !data->len)
goto done;
if (data->status != 200)
goto done;
data->data = (char*)realloc(data->data, data->len + 1); room_data = (char*)malloc(data->len + 1);
data->data[data->len] = '\0'; if (!room_data)
goto done;
memcpy(room_data, data->data, data->len);
room_data[data->len] = '\0';
netplay_rooms_parse(data->data); netplay_rooms_parse(room_data);
room = netplay_room_get(0); free(room_data);
room = netplay_room_get(0);
if (room) if (room)
{ {
if (room->host_method == NETPLAY_HOST_METHOD_MITM)
snprintf(join_hostname, sizeof(join_hostname), "%s|%d|%s",
room->mitm_address, room->mitm_port, room->mitm_session);
else
snprintf(join_hostname, sizeof(join_hostname), "%s|%d",
room->address, room->port);
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL)) if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL))
deinit_netplay(); deinit_netplay();
netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL); netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL);
task_push_netplay_crc_scan(room->gamecrc, if (room->host_method == NETPLAY_HOST_METHOD_MITM)
room->gamename, join_hostname, room->corename, room->subsystem_name); snprintf(hostname, sizeof(hostname), "%s|%d|%s",
room->mitm_address, room->mitm_port, room->mitm_session);
else
snprintf(hostname, sizeof(hostname), "%s|%d",
room->address, room->port);
task_push_netplay_crc_scan(room->gamecrc, room->gamename, hostname,
room->corename, room->subsystem_name);
discord_st->connecting = true; discord_st->connecting = true;
if (discord_st->ready) if (discord_st->ready)
discord_update(PRESENCE_NETPLAY_CLIENT); discord_update(PRESENCE_NETPLAY_CLIENT);
} }
finish: netplay_rooms_free();
if (user_data)
free(user_data); done:
free(user_data);
} }
static void handle_discord_join(const char* secret) static void handle_discord_join(const char *secret)
{ {
char url[2048]; char url[512];
struct string_list *list = string_split(secret, "|");
discord_state_t *discord_st = &discord_state_st; discord_state_t *discord_st = &discord_state_st;
int room_id = (int)strtol(secret, NULL, 10);
strlcpy(discord_st->peer_party_id, if (room_id)
list->elems[0].data, sizeof(discord_st->peer_party_id)); {
snprintf(url, sizeof(url), FILE_PATH_LOBBY_LIBRETRO_URL "%s/", snprintf(discord_st->peer_party_id, sizeof(discord_st->peer_party_id),
discord_st->peer_party_id); "%d", room_id);
task_push_http_transfer(url, true, NULL, handle_discord_join_cb, NULL); strlcpy(url, FILE_PATH_LOBBY_LIBRETRO_URL, sizeof(url));
strlcat(url, discord_st->peer_party_id, sizeof(url));
task_push_http_transfer(url, true, NULL, handle_discord_join_cb, NULL);
}
} }
static void handle_discord_spectate(const char* secret) static void handle_discord_spectate(const char *secret)
{ {
} }
#ifdef HAVE_MENU
#if 0 #if 0
#ifdef HAVE_MENU
static void handle_discord_join_response(void *ignore, const char *line) static void handle_discord_join_response(void *ignore, const char *line)
{ {
/* TODO/FIXME: needs in-game widgets */ /* TODO/FIXME: needs in-game widgets */
if (strstr(line, "yes")) if (strstr(line, "yes"))
Discord_Respond(user_id, DISCORD_REPLY_YES); Discord_Respond(user_id, DISCORD_REPLY_YES);
#ifdef HAVE_MENU
menu_input_dialog_end(); menu_input_dialog_end();
retroarch_menu_running_finished(false); retroarch_menu_running_finished(false);
#endif
} }
#endif #endif
#endif #endif
static void handle_discord_join_request(const DiscordUser* request) static void handle_discord_join_request(const DiscordUser *request)
{ {
#ifdef HAVE_MENU #ifdef HAVE_MENU
#if 0 #if 0
char buf[PATH_MAX_LENGTH]; char buf[PATH_MAX_LENGTH];
menu_input_ctx_line_t line; menu_input_ctx_line_t line = {0};
#endif #endif
discord_state_t *discord_st = &discord_state_st; discord_state_t *discord_st = &discord_state_st;
discord_download_avatar(discord_st, request->userId, request->avatar); discord_download_avatar(discord_st, request->userId, request->avatar);
#if 0 #if 0
/* TODO/FIXME: Needs in-game widgets */ /* TODO/FIXME: Needs in-game widgets */
retroarch_menu_running(); retroarch_menu_running();
memset(&line, 0, sizeof(line));
snprintf(buf, sizeof(buf), "%s %s?", snprintf(buf, sizeof(buf), "%s %s?",
msg_hash_to_str(MSG_DISCORD_CONNECTION_REQUEST), request->username); msg_hash_to_str(MSG_DISCORD_CONNECTION_REQUEST), request->username);
line.label = buf; line.label = buf;
line.label_setting = "no_setting"; line.label_setting = "no_setting";
line.cb = handle_discord_join_response; line.cb = handle_discord_join_response;

View File

@ -23,8 +23,6 @@
#include <stddef.h> #include <stddef.h>
#include <boolean.h> #include <boolean.h>
#include <libretro.h>
#include <retro_miscellaneous.h>
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
#include "../../config.h" #include "../../config.h"
@ -32,16 +30,14 @@
#include <net/net_compat.h> #include <net/net_compat.h>
#include "../../retroarch_types.h"
#include "../natt.h" #include "../natt.h"
#include "netplay_protocol.h"
#define NETPLAY_NICK_LEN 32 #define NETPLAY_NICK_LEN 32
#define NETPLAY_HOST_STR_LEN 32 #define NETPLAY_HOST_STR_LEN 32
#define NETPLAY_HOST_LONGSTR_LEN 256 #define NETPLAY_HOST_LONGSTR_LEN 256
#define NETPLAY_MITM_SERVERS 5
#define NETPLAY_CHAT_MAX_MESSAGES 5 #define NETPLAY_CHAT_MAX_MESSAGES 5
#define NETPLAY_CHAT_MAX_SIZE 96 #define NETPLAY_CHAT_MAX_SIZE 96
#define NETPLAY_CHAT_FRAME_TIME 900 #define NETPLAY_CHAT_FRAME_TIME 900
@ -132,10 +128,10 @@ enum netplay_host_method
enum rarch_netplay_discovery_ctl_state enum rarch_netplay_discovery_ctl_state
{ {
RARCH_NETPLAY_DISCOVERY_CTL_NONE = 0, RARCH_NETPLAY_DISCOVERY_CTL_NONE = 0,
RARCH_NETPLAY_DISCOVERY_CTL_LAN_SEND_QUERY, RARCH_NETPLAY_DISCOVERY_CTL_LAN_SEND_QUERY,
RARCH_NETPLAY_DISCOVERY_CTL_LAN_GET_RESPONSES, RARCH_NETPLAY_DISCOVERY_CTL_LAN_GET_RESPONSES,
RARCH_NETPLAY_DISCOVERY_CTL_LAN_CLEAR_RESPONSES RARCH_NETPLAY_DISCOVERY_CTL_LAN_CLEAR_RESPONSES
}; };
typedef struct netplay netplay_t; typedef struct netplay netplay_t;
@ -143,62 +139,38 @@ typedef struct netplay netplay_t;
typedef struct netplay_client_info typedef struct netplay_client_info
{ {
uint32_t protocol; uint32_t protocol;
int32_t ping; int32_t ping;
int id; int id;
enum rarch_netplay_connection_mode mode; enum rarch_netplay_connection_mode mode;
char name[NETPLAY_NICK_LEN]; char name[NETPLAY_NICK_LEN];
} netplay_client_info_t; } netplay_client_info_t;
struct ad_packet
{
uint32_t header;
int32_t content_crc;
int32_t port;
uint32_t has_password;
char nick[NETPLAY_NICK_LEN];
char frontend[NETPLAY_HOST_STR_LEN];
char core[NETPLAY_HOST_STR_LEN];
char core_version[NETPLAY_HOST_STR_LEN];
char retroarch_version[NETPLAY_HOST_STR_LEN];
char content[NETPLAY_HOST_LONGSTR_LEN];
char subsystem_name[NETPLAY_HOST_LONGSTR_LEN];
};
typedef struct mitm_server typedef struct mitm_server
{ {
const char *name; const char *name;
const char *description; const char *description;
} mitm_server_t; } mitm_server_t;
static const mitm_server_t netplay_mitm_server_list[] = {
{ "nyc", "New York City, USA" },
{ "madrid", "Madrid, Spain" },
{ "saopaulo", "Sao Paulo, Brazil" },
{ "singapore", "Singapore" },
{ "custom", "Custom" },
};
struct netplay_room struct netplay_room
{ {
struct netplay_room *next; struct netplay_room *next;
int id; int id;
int gamecrc;
int port; int port;
int mitm_port; int mitm_port;
int gamecrc;
int timestamp;
int host_method; int host_method;
char country [3]; char nickname[NETPLAY_NICK_LEN];
char retroarch_version [33]; char frontend[NETPLAY_HOST_STR_LEN];
char nickname [33]; char corename[NETPLAY_HOST_STR_LEN];
char subsystem_name [256]; char coreversion[NETPLAY_HOST_STR_LEN];
char corename [256]; char retroarch_version[NETPLAY_HOST_STR_LEN];
char frontend [256]; char gamename[NETPLAY_HOST_LONGSTR_LEN];
char coreversion [256]; char subsystem_name[NETPLAY_HOST_LONGSTR_LEN];
char gamename [256]; char country[3];
char address [256]; char address[NETPLAY_HOST_LONGSTR_LEN];
char mitm_handle [33]; char mitm_handle[NETPLAY_HOST_STR_LEN];
char mitm_address [256]; char mitm_address[NETPLAY_HOST_LONGSTR_LEN];
char mitm_session [33]; char mitm_session[NETPLAY_HOST_STR_LEN];
bool has_password; bool has_password;
bool has_spectate_password; bool has_spectate_password;
bool connectable; bool connectable;
@ -216,7 +188,7 @@ struct netplay_host
{ {
int content_crc; int content_crc;
int port; int port;
char address[NETPLAY_HOST_STR_LEN]; char address[16];
char nick[NETPLAY_NICK_LEN]; char nick[NETPLAY_NICK_LEN];
char frontend[NETPLAY_HOST_STR_LEN]; char frontend[NETPLAY_HOST_STR_LEN];
char core[NETPLAY_HOST_STR_LEN]; char core[NETPLAY_HOST_STR_LEN];
@ -231,27 +203,17 @@ struct netplay_host
struct netplay_host_list struct netplay_host_list
{ {
struct netplay_host *hosts; struct netplay_host *hosts;
size_t allocated;
size_t size; size_t size;
}; };
struct netplay_chat
{
struct
{
uint32_t frames;
char nick[NETPLAY_NICK_LEN];
char msg[NETPLAY_CHAT_MAX_SIZE];
} messages[NETPLAY_CHAT_MAX_MESSAGES];
uint32_t message_slots;
};
struct netplay_chat_buffer struct netplay_chat_buffer
{ {
struct struct
{ {
uint8_t alpha; uint8_t alpha;
char nick[NETPLAY_NICK_LEN]; char nick[NETPLAY_NICK_LEN];
char msg[NETPLAY_CHAT_MAX_SIZE]; char msg[NETPLAY_CHAT_MAX_SIZE];
} messages[NETPLAY_CHAT_MAX_MESSAGES]; } messages[NETPLAY_CHAT_MAX_MESSAGES];
}; };
@ -260,8 +222,6 @@ typedef struct
/* NAT traversal info (if NAT traversal is used and serving) */ /* NAT traversal info (if NAT traversal is used and serving) */
struct nat_traversal_data nat_traversal_request; struct nat_traversal_data nat_traversal_request;
#ifdef HAVE_NETPLAYDISCOVERY #ifdef HAVE_NETPLAYDISCOVERY
/* Packet buffer for advertisement and responses */
struct ad_packet ad_packet_buffer;
/* List of discovered hosts */ /* List of discovered hosts */
struct netplay_host_list discovered_hosts; struct netplay_host_list discovered_hosts;
#endif #endif
@ -272,21 +232,15 @@ typedef struct
/* Used while Netplay is running */ /* Used while Netplay is running */
netplay_t *data; netplay_t *data;
netplay_client_info_t *client_info; netplay_client_info_t *client_info;
/* Chat messages */
struct netplay_chat *chat;
size_t client_info_count; size_t client_info_count;
#ifdef HAVE_NETPLAYDISCOVERY #ifdef HAVE_NETPLAYDISCOVERY
size_t discovered_hosts_allocated;
/* LAN discovery sockets */ /* LAN discovery sockets */
int lan_ad_server_fd; int lan_ad_server_fd;
int lan_ad_client_fd; int lan_ad_client_fd;
#endif #endif
int room_count; int room_count;
int reannounce;
int reping;
int latest_ping; int latest_ping;
unsigned server_port_deferred; unsigned server_port_deferred;
uint16_t mapping[RETROK_LAST];
char server_address_deferred[256]; char server_address_deferred[256];
char server_session_deferred[32]; char server_session_deferred[32];
bool netplay_client_deferred; bool netplay_client_deferred;
@ -302,101 +256,16 @@ typedef struct
net_driver_state_t *networking_state_get_ptr(void); net_driver_state_t *networking_state_get_ptr(void);
bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data); bool netplay_decode_hostname(const char *hostname,
char *address, unsigned *port, char *session, size_t len);
bool netplay_is_lan_address(struct sockaddr_in *addr);
bool netplay_6to4(struct sockaddr_storage *addr);
int netplay_rooms_parse(const char *buf); int netplay_rooms_parse(const char *buf);
struct netplay_room* netplay_room_get(int index);
int netplay_rooms_get_count(void); int netplay_rooms_get_count(void);
struct netplay_room *netplay_room_get(int index);
void netplay_rooms_free(void); void netplay_rooms_free(void);
/**
* netplay_frontend_paused
* @netplay : pointer to netplay object
* @paused : true if frontend is paused
*
* Inform Netplay of the frontend's pause state (paused or otherwise)
*/
void netplay_frontend_paused(netplay_t *netplay, bool paused);
/**
* netplay_toggle_play_spectate
*
* Toggle between play mode and spectate mode
*/
void netplay_toggle_play_spectate(netplay_t *netplay);
/**
* netplay_input_chat
*
* Opens an input menu for sending netplay chat
*/
void netplay_input_chat(netplay_t *netplay);
/**
* netplay_load_savestate
* @netplay : pointer to netplay object
* @serial_info : the savestate being loaded, NULL means
* "load it yourself"
* @save : Whether to save the provided serial_info
* into the frame buffer
*
* Inform Netplay of a savestate load and send it to the other side
**/
void netplay_load_savestate(netplay_t *netplay,
retro_ctx_serialize_info_t *serial_info, bool save);
/**
* netplay_core_reset
* @netplay : pointer to netplay object
*
* Indicate that the core has been reset to netplay peers
**/
void netplay_core_reset(netplay_t *netplay);
int16_t netplay_input_state(netplay_t *netplay,
unsigned port, unsigned device,
unsigned idx, unsigned id);
/**
* netplay_poll:
* @netplay : pointer to netplay object
*
* Polls network to see if we have anything new. If our
* network buffer is full, we simply have to block
* for new input data.
*
* Returns: true (1) if successful, otherwise false (0).
**/
bool netplay_poll(
bool block_libretro_input,
void *settings_data,
netplay_t *netplay);
/**
* netplay_is_alive:
* @netplay : pointer to netplay object
*
* Checks if input port/index is controlled by netplay or not.
*
* Returns: true (1) if alive, otherwise false (0).
**/
bool netplay_is_alive(netplay_t *netplay);
/**
* netplay_should_skip:
* @netplay : pointer to netplay object
*
* If we're fast-forward replaying to resync, check if we
* should actually show frame.
*
* Returns: bool (1) if we should skip this frame, otherwise
* false (0).
**/
bool netplay_should_skip(netplay_t *netplay);
/** /**
* init_netplay * init_netplay
* @server : server address to connect to (client only) * @server : server address to connect to (client only)
@ -410,33 +279,22 @@ bool netplay_should_skip(netplay_t *netplay);
* Returns: true (1) if successful, otherwise false (0). * Returns: true (1) if successful, otherwise false (0).
**/ **/
bool init_netplay(const char *server, unsigned port, const char *mitm_session); bool init_netplay(const char *server, unsigned port, const char *mitm_session);
bool init_netplay_deferred(const char *server, unsigned port,
bool init_netplay_deferred(const char *server, unsigned port, const char *mitm_session); const char *mitm_session);
void deinit_netplay(void); void deinit_netplay(void);
void video_frame_net(const void *data, unsigned width, bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data);
unsigned height, size_t pitch);
void audio_sample_net(int16_t left, int16_t right);
size_t audio_sample_batch_net(const int16_t *data, size_t frames);
int16_t input_state_net(unsigned port, unsigned device,
unsigned idx, unsigned id);
#ifdef HAVE_NETPLAYDISCOVERY #ifdef HAVE_NETPLAYDISCOVERY
/** Initialize Netplay discovery */ /** Initialize Netplay discovery */
bool init_netplay_discovery(void); bool init_netplay_discovery(void);
/** Deinitialize and free Netplay discovery */ /** Deinitialize and free Netplay discovery */
void deinit_netplay_discovery(void); void deinit_netplay_discovery(void);
/** Discovery control */ /** Discovery control */
bool netplay_discovery_driver_ctl( bool netplay_discovery_driver_ctl(enum rarch_netplay_discovery_ctl_state state,
enum rarch_netplay_discovery_ctl_state state, void *data); void *data);
#endif #endif
bool netplay_decode_hostname(const char *hostname, extern const mitm_server_t netplay_mitm_server_list[NETPLAY_MITM_SERVERS];
char *address, unsigned *port, char *session, size_t len);
bool netplay_is_lan_address(struct sockaddr_in *addr);
bool netplay_6to4(struct sockaddr_storage *addr);
#endif #endif

File diff suppressed because it is too large Load Diff

View File

@ -20,25 +20,30 @@
#define __RARCH_NETPLAY_PRIVATE_H #define __RARCH_NETPLAY_PRIVATE_H
#include "netplay.h" #include "netplay.h"
#include "netplay_protocol.h"
#include <libretro.h>
#include <retro_miscellaneous.h>
#include <streams/trans_stream.h> #include <streams/trans_stream.h>
#include "../../retroarch_types.h"
#define RARCH_DEFAULT_PORT 55435 #define RARCH_DEFAULT_PORT 55435
#define RARCH_DEFAULT_NICK "Anonymous" #define RARCH_DEFAULT_NICK "Anonymous"
#define NETPLAY_PASS_LEN 128 #define NETPLAY_PASS_LEN 128
#define NETPLAY_PASS_HASH_LEN 64 /* length of a SHA-256 hash */ #define NETPLAY_PASS_HASH_LEN 64 /* length of a SHA-256 hash */
#define MAX_SERVER_STALL_TIME_USEC (5*1000*1000) #define MAX_SERVER_STALL_TIME_USEC (5*1000*1000)
#define MAX_CLIENT_STALL_TIME_USEC (10*1000*1000) #define MAX_CLIENT_STALL_TIME_USEC (10*1000*1000)
#define CATCH_UP_CHECK_TIME_USEC (500*1000) #define CATCH_UP_CHECK_TIME_USEC (500*1000)
#define MAX_RETRIES 16 #define MAX_RETRIES 16
#define RETRY_MS 500 #define RETRY_MS 500
#define MAX_INPUT_DEVICES 16 #define MAX_INPUT_DEVICES 16
/* We allow only 32 clients to fit into a 32-bit bitmap */ /* We allow only 32 clients to fit into a 32-bit bitmap */
#define MAX_CLIENTS 32 #define MAX_CLIENTS 32
typedef uint32_t client_bitmap_t;
/* Because the callback keyboard reverses some assumptions, when the keyboard /* Because the callback keyboard reverses some assumptions, when the keyboard
* callbacks are in use, we assign a pseudodevice for it */ * callbacks are in use, we assign a pseudodevice for it */
@ -63,8 +68,9 @@ typedef uint32_t client_bitmap_t;
/* Mapping of serialization quirks to netplay quirks. */ /* Mapping of serialization quirks to netplay quirks. */
#define NETPLAY_QUIRK_MAP_UNDERSTOOD (\ #define NETPLAY_QUIRK_MAP_UNDERSTOOD (\
RETRO_SERIALIZATION_QUIRK_INCOMPLETE |\ RETRO_SERIALIZATION_QUIRK_INCOMPLETE |\
RETRO_SERIALIZATION_QUIRK_CORE_VARIABLE_SIZE |\
RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE |\ RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE |\
RETRO_SERIALIZATION_QUIRK_CORE_VARIABLE_SIZE |\
RETRO_SERIALIZATION_QUIRK_FRONT_VARIABLE_SIZE |\
RETRO_SERIALIZATION_QUIRK_SINGLE_SESSION |\ RETRO_SERIALIZATION_QUIRK_SINGLE_SESSION |\
RETRO_SERIALIZATION_QUIRK_ENDIAN_DEPENDENT |\ RETRO_SERIALIZATION_QUIRK_ENDIAN_DEPENDENT |\
RETRO_SERIALIZATION_QUIRK_PLATFORM_DEPENDENT \ RETRO_SERIALIZATION_QUIRK_PLATFORM_DEPENDENT \
@ -89,6 +95,18 @@ typedef uint32_t client_bitmap_t;
#define NETPLAY_COMPRESSION_SUPPORTED 0 #define NETPLAY_COMPRESSION_SUPPORTED 0
#endif #endif
/* The keys supported by netplay */
enum netplay_keys
{
NETPLAY_KEY_UNKNOWN = 0,
#define K(k) NETPLAY_KEY_ ## k,
#define KL(k,l) K(k)
#include "netplay_keys.h"
#undef KL
#undef K
NETPLAY_KEY_LAST
};
enum netplay_cmd enum netplay_cmd
{ {
/* Basic commands */ /* Basic commands */
@ -194,11 +212,11 @@ enum netplay_cmd
NETPLAY_CMD_SETTING_INPUT_LATENCY_FRAMES = 0x2001 NETPLAY_CMD_SETTING_INPUT_LATENCY_FRAMES = 0x2001
}; };
#define NETPLAY_CMD_SYNC_BIT_PAUSED (1U<<31) #define NETPLAY_CMD_SYNC_BIT_PAUSED (1U<<31)
#define NETPLAY_CMD_PLAY_BIT_SLAVE (1U<<31) #define NETPLAY_CMD_PLAY_BIT_SLAVE (1U<<31)
#define NETPLAY_CMD_MODE_BIT_YOU (1U<<31) #define NETPLAY_CMD_MODE_BIT_YOU (1U<<31)
#define NETPLAY_CMD_MODE_BIT_PLAYING (1U<<30) #define NETPLAY_CMD_MODE_BIT_PLAYING (1U<<30)
#define NETPLAY_CMD_MODE_BIT_SLAVE (1U<<29) #define NETPLAY_CMD_MODE_BIT_SLAVE (1U<<29)
/* These are the reasons given for mode changes to be rejected */ /* These are the reasons given for mode changes to be rejected */
enum netplay_cmd_mode_reasons enum netplay_cmd_mode_reasons
@ -224,22 +242,22 @@ enum rarch_netplay_share_preference
{ {
/* Prefer not to share, shouldn't be set /* Prefer not to share, shouldn't be set
as a sharing mode for an shared device */ as a sharing mode for an shared device */
NETPLAY_SHARE_NO_SHARING = 0x0, NETPLAY_SHARE_NO_SHARING = 0x00,
/* No preference. Only for requests. /* No preference. Only for requests.
Set if sharing is requested but either Set if sharing is requested but either
* digital or analog doesn't have a preference. */ * digital or analog doesn't have a preference. */
NETPLAY_SHARE_NO_PREFERENCE = 0x1, NETPLAY_SHARE_NO_PREFERENCE = 0x01,
/* For digital devices */ /* For digital devices */
NETPLAY_SHARE_DIGITAL_BITS = 0x1C, NETPLAY_SHARE_DIGITAL_BITS = 0x1C,
NETPLAY_SHARE_DIGITAL_OR = 0x4, NETPLAY_SHARE_DIGITAL_OR = 0x04,
NETPLAY_SHARE_DIGITAL_XOR = 0x8, NETPLAY_SHARE_DIGITAL_XOR = 0x08,
NETPLAY_SHARE_DIGITAL_VOTE = 0xC, NETPLAY_SHARE_DIGITAL_VOTE = 0x0C,
/* For analog devices */ /* For analog devices */
NETPLAY_SHARE_ANALOG_BITS = 0xE0, NETPLAY_SHARE_ANALOG_BITS = 0xE0,
NETPLAY_SHARE_ANALOG_MAX = 0x20, NETPLAY_SHARE_ANALOG_MAX = 0x20,
NETPLAY_SHARE_ANALOG_AVERAGE = 0x40 NETPLAY_SHARE_ANALOG_AVERAGE = 0x40
}; };
@ -300,7 +318,7 @@ struct delta_frame
/* The simulated input. is_real here means the simulation is done, i.e., /* The simulated input. is_real here means the simulation is done, i.e.,
* it's a real simulation, not real input. */ * it's a real simulation, not real input. */
netplay_input_state_t simlated_input[MAX_INPUT_DEVICES]; netplay_input_state_t simulated_input[MAX_INPUT_DEVICES];
/* The serialized state of the core at this frame, before input */ /* The serialized state of the core at this frame, before input */
void *state; void *state;
@ -339,14 +357,18 @@ struct netplay_connection
/* Timer used to estimate a connection's latency */ /* Timer used to estimate a connection's latency */
retro_time_t ping_timer; retro_time_t ping_timer;
/* Address of peer */
struct sockaddr_storage addr;
/* Buffers for sending and receiving data */ /* Buffers for sending and receiving data */
struct socket_buffer send_packet_buffer, recv_packet_buffer; struct socket_buffer send_packet_buffer;
struct socket_buffer recv_packet_buffer;
/* fd associated with this connection */ /* What compression does this peer support? */
int fd; uint32_t compression_supported;
/* Salt associated with password transaction */
uint32_t salt;
/* Which netplay protocol is this connection running? */
uint32_t netplay_protocol;
/* If the mode is a DELAYED_DISCONNECT or SPECTATOR, /* If the mode is a DELAYED_DISCONNECT or SPECTATOR,
* the transmission of the mode change may have to * the transmission of the mode change may have to
@ -355,43 +377,37 @@ struct netplay_connection
* is active. */ * is active. */
uint32_t delay_frame; uint32_t delay_frame;
/* What compression does this peer support? */
uint32_t compression_supported;
/* For the server: When was the last time we requested /* For the server: When was the last time we requested
* this client to stall? * this client to stall?
* For the client: How many frames of stall do we have left? */ * For the client: How many frames of stall do we have left? */
uint32_t stall_frame; uint32_t stall_frame;
/* Salt associated with password transaction */
uint32_t salt;
/* Which netplay protocol is this connection running? */
uint32_t netplay_protocol;
/* What latency is this connection running on? /* What latency is this connection running on?
* Network latency has limited precision as we estimate it * Network latency has limited precision as we estimate it
* once every pre-frame. */ * once every pre-frame. */
int32_t ping; int32_t ping;
/* Is this connection stalling? */ /* fd associated with this connection */
enum rarch_netplay_stall_reason stall; int fd;
/* Mode of the connection */ /* Mode of the connection */
enum rarch_netplay_connection_mode mode; enum rarch_netplay_connection_mode mode;
/* Is this connection stalling? */
enum rarch_netplay_stall_reason stall;
/* Nickname of peer */ /* Nickname of peer */
char nick[NETPLAY_NICK_LEN]; char nick[NETPLAY_NICK_LEN];
/* Is this connection buffer in use? */
bool active;
/* Is this player paused? */ /* Is this player paused? */
bool paused; bool paused;
/* Is this connection allowed to play (server only)? */ /* Is this connection allowed to play (server only)? */
bool can_play; bool can_play;
/* Is this connection buffer in use? */
bool active;
/* Did we request a ping response? */ /* Did we request a ping response? */
bool ping_requested; bool ping_requested;
}; };
@ -400,8 +416,8 @@ struct netplay_connection
struct compression_transcoder struct compression_transcoder
{ {
const struct trans_stream_backend *compression_backend; const struct trans_stream_backend *compression_backend;
void *compression_stream;
const struct trans_stream_backend *decompression_backend; const struct trans_stream_backend *decompression_backend;
void *compression_stream;
void *decompression_stream; void *decompression_stream;
}; };
@ -423,42 +439,52 @@ struct netplay_mitm_pending
int fds[NETPLAY_MITM_MAX_PENDING]; int fds[NETPLAY_MITM_MAX_PENDING];
}; };
struct netplay_chat
{
struct
{
uint32_t frames;
char nick[NETPLAY_NICK_LEN];
char msg[NETPLAY_CHAT_MAX_SIZE];
} messages[NETPLAY_CHAT_MAX_MESSAGES];
};
struct netplay struct netplay
{ {
/* Quirks in the savestate implementation */ /* Quirks in the savestate implementation */
uint64_t quirks; uint64_t quirks;
/* We stall if we're far enough ahead that we
* couldn't transparently rewind.
* To know if we could transparently rewind,
* we need to know how long running a frame takes.
* We record that every frame and get a running (window) average. */
retro_time_t frame_run_time[NETPLAY_FRAME_RUN_TIME_WINDOW];
retro_time_t frame_run_time_sum;
retro_time_t frame_run_time_avg;
/* When did we start falling behind? */ /* When did we start falling behind? */
retro_time_t catch_up_time; retro_time_t catch_up_time;
/* How long have we been stalled? */ /* How long have we been stalled? */
retro_time_t stall_time; retro_time_t stall_time;
/* We stall if we're far enough ahead that we
* couldn't transparently rewind.
* To know if we could transparently rewind,
* we need to know how long running a frame takes.
* We record that every frame and get a running (window) average. */
retro_time_t frame_run_time[NETPLAY_FRAME_RUN_TIME_WINDOW];
retro_time_t frame_run_time_sum, frame_run_time_avg;
struct retro_callbacks cbs; struct retro_callbacks cbs;
/* Compression transcoder */ /* Compression transcoder */
struct compression_transcoder compress_nil, struct compression_transcoder compress_nil;
compress_zlib; struct compression_transcoder compress_zlib;
/* MITM session id */ /* MITM session id */
mitm_id_t mitm_session_id; mitm_id_t mitm_session_id;
struct netplay_connection one_connection; /* Client only */ /* Chat messages */
/* All of our connections */ struct netplay_chat chat;
struct netplay_connection *connections;
/* MITM connection handler */ /* MITM connection handler */
struct netplay_mitm_pending *mitm_pending; struct netplay_mitm_pending *mitm_pending;
/* Our local socket info */ /* All of our connections */
struct addrinfo *addr; struct netplay_connection *connections;
struct delta_frame *buffer; struct delta_frame *buffer;
@ -475,21 +501,21 @@ struct netplay
/* The frame we're currently inputting */ /* The frame we're currently inputting */
size_t self_ptr; size_t self_ptr;
/* The frame we're currently running, which may be /* The frame we're currently running, which may be
* behind the frame we're currently inputting if * behind the frame we're currently inputting if
* we're using input latency */ * we're using input latency */
size_t run_ptr; size_t run_ptr;
/* The first frame at which some data might be unreliable */ /* The first frame at which some data might be unreliable */
size_t other_ptr; size_t other_ptr;
/* Pointer to the first frame for which we're missing /* Pointer to the first frame for which we're missing
* the data of at least one connected player excluding ourself. * the data of at least one connected player excluding ourself.
* Generally, other_ptr <= unread_ptr <= self_ptr, * Generally, other_ptr <= unread_ptr <= self_ptr,
* but unread_ptr can get ahead of self_ptr if the peer * but unread_ptr can get ahead of self_ptr if the peer
* is running fast. */ * is running fast. */
size_t unread_ptr; size_t unread_ptr;
/* Pointer to the next frame to read from each client */ /* Pointer to the next frame to read from each client */
size_t read_ptr[MAX_CLIENTS]; size_t read_ptr[MAX_CLIENTS];
/* Pointer to the next frame to read from the server /* Pointer to the next frame to read from the server
* (as it might not be a player but still synchronizes) * (as it might not be a player but still synchronizes)
*/ */
size_t server_ptr; size_t server_ptr;
@ -499,9 +525,6 @@ struct netplay
/* Pseudo random seed */ /* Pseudo random seed */
unsigned long simple_rand_next; unsigned long simple_rand_next;
/* TCP connection for listening (server only) */
int listen_fd;
/* Our client number */ /* Our client number */
uint32_t self_client_num; uint32_t self_client_num;
@ -516,15 +539,11 @@ struct netplay
uint32_t client_devices[MAX_CLIENTS]; uint32_t client_devices[MAX_CLIENTS];
/* For each device, the bitmap of clients connected */ /* For each device, the bitmap of clients connected */
client_bitmap_t device_clients[MAX_INPUT_DEVICES]; uint32_t device_clients[MAX_INPUT_DEVICES];
/* Our own device bitmap */ /* Our own device bitmap */
uint32_t self_devices; uint32_t self_devices;
/* Number of desync operations we're currently performing.
* If set, we don't attempt to stay in sync. */
uint32_t desync;
/* The device types for every connected device. /* The device types for every connected device.
* We store them and ignore any menu changes, * We store them and ignore any menu changes,
* as netplay needs fixed devices. */ * as netplay needs fixed devices. */
@ -538,31 +557,46 @@ struct netplay
uint32_t server_frame_count; uint32_t server_frame_count;
uint32_t replay_frame_count; uint32_t replay_frame_count;
int frame_run_time_ptr;
/* Counter for timeouts */
unsigned timeout_cnt;
/* Latency frames; positive to hide network latency,
* negative to hide input latency */
int input_latency_frames;
/* Frequency with which to check CRCs */
int check_frames;
/* How far behind did we fall? */ /* How far behind did we fall? */
uint32_t catch_up_behind; uint32_t catch_up_behind;
/* Number of desync operations we're currently performing.
* If set, we don't attempt to stay in sync. */
uint32_t desync;
/* Host settings */ /* Host settings */
int32_t input_latency_frames_min; int32_t input_latency_frames_min;
int32_t input_latency_frames_max; int32_t input_latency_frames_max;
/* Are we stalled? */ /* Counter for timeouts */
enum rarch_netplay_stall_reason stall; unsigned timeout_cnt;
/* TCP connection for listening (server only) */
int listen_fd;
int frame_run_time_ptr;
/* Frequency with which to check CRCs */
int check_frames;
/* Latency frames; positive to hide network latency,
* negative to hide input latency */
int input_latency_frames;
int reannounce;
int reping;
/* Our mode and status */ /* Our mode and status */
enum rarch_netplay_connection_mode self_mode; enum rarch_netplay_connection_mode self_mode;
/* Are we stalled? */
enum rarch_netplay_stall_reason stall;
/* Keyboard mapping (network and host) */
uint16_t mapping_hton[RETROK_LAST];
uint16_t mapping_ntoh[NETPLAY_KEY_LAST];
/* TCP port (only set if serving) */ /* TCP port (only set if serving) */
uint16_t tcp_port; uint16_t tcp_port;
uint16_t ext_tcp_port; uint16_t ext_tcp_port;
@ -573,8 +607,6 @@ struct netplay
/* Our nickname */ /* Our nickname */
char nick[NETPLAY_NICK_LEN]; char nick[NETPLAY_NICK_LEN];
bool nat_traversal;
/* Set to true if we have a device that most cores /* Set to true if we have a device that most cores
* translate to "up/down" actions, typically a keyboard. * translate to "up/down" actions, typically a keyboard.
* We need to keep track of this because with such a device, * We need to keep track of this because with such a device,
@ -583,11 +615,30 @@ struct netplay
* up/down states will proceed as expected. */ * up/down states will proceed as expected. */
bool have_updown_device; bool have_updown_device;
/* If true, never progress without peer input
* (stateless/rewindless mode) */
bool stateless_mode;
/* Are we the server? */
bool is_server;
bool nat_traversal;
/* Have we checked whether CRCs are valid at all? */
bool crc_validity_checked;
/* Are they valid? */
bool crcs_valid;
/* Netplay pausing */
bool local_paused;
bool remote_paused;
/* Are we replaying old frames? */ /* Are we replaying old frames? */
bool is_replay; bool is_replay;
/* We don't want to poll several times on a frame. */ /* Opposite of stalling, should we be catching up? */
bool can_poll; bool catch_up;
/* Force a rewind to other_frame_count/other_ptr. /* Force a rewind to other_frame_count/other_ptr.
* This is for synchronized events, such as restarting * This is for synchronized events, such as restarting
@ -603,33 +654,17 @@ struct netplay
/* Have we requested a savestate as a sync point? */ /* Have we requested a savestate as a sync point? */
bool savestate_request_outstanding; bool savestate_request_outstanding;
/* Netplay pausing */
bool local_paused;
bool remote_paused;
/* If true, never progress without peer input
* (stateless/rewindless mode) */
bool stateless_mode;
/* Opposite of stalling, should we be catching up? */
bool catch_up;
/* Have we checked whether CRCs are valid at all? */
bool crc_validity_checked;
/* Are they valid? */
bool crcs_valid;
/* Are we the server? */
bool is_server;
/* Are we the connected? */
bool is_connected;
/* Host settings */ /* Host settings */
bool allow_pausing; bool allow_pausing;
}; };
void video_frame_net(const void *data,
unsigned width, unsigned height, size_t pitch);
void audio_sample_net(int16_t left, int16_t right);
size_t audio_sample_batch_net(const int16_t *data, size_t frames);
int16_t input_state_net(unsigned port, unsigned device,
unsigned idx, unsigned id);
/*************************************************************** /***************************************************************
* NETPLAY-BUF.C * NETPLAY-BUF.C
**************************************************************/ **************************************************************/
@ -699,69 +734,22 @@ bool netplay_delta_frame_ready(netplay_t *netplay,
struct delta_frame *delta, struct delta_frame *delta,
uint32_t frame); uint32_t frame);
/**
* netplay_input_state_for
*
* Get an input state for a particular client
*/
netplay_input_state_t netplay_input_state_for(
netplay_input_state_t *list,
uint32_t client_num, size_t size,
bool must_create, bool must_not_create);
/**
* netplay_expected_input_size
*
* Size in words for a given set of devices.
*/
uint32_t netplay_expected_input_size(netplay_t *netplay,
uint32_t devices);
/*************************************************************** /***************************************************************
* NETPLAY-FRONTEND.C * NETPLAY-FRONTEND.C
**************************************************************/ **************************************************************/
/** /**
* input_poll_net * input_poll_net
* @netplay : pointer to netplay object
* *
* Poll the network if necessary. * Poll the network if necessary.
*/ */
void input_poll_net(void); void input_poll_net(netplay_t *netplay);
/***************************************************************
* NETPLAY-HANDSHAKE.C
**************************************************************/
/**
* netplay_handshake_init_send
*
* Initialize our handshake and send the first
* part of the handshake protocol.
*/
bool netplay_handshake_init_send(netplay_t *netplay,
struct netplay_connection *connection, uint32_t protocol);
/**
* netplay_handshake
*
* Data receiver for all handshake states.
*/
bool netplay_handshake(netplay_t *netplay,
struct netplay_connection *connection, bool *had_input);
/*************************************************************** /***************************************************************
* NETPLAY-INIT.C * NETPLAY-INIT.C
**************************************************************/ **************************************************************/
/**
* netplay_try_init_serialization
*
* Try to initialize serialization. For quirky cores.
*
* Returns true if serialization is now ready, false otherwise.
*/
bool netplay_try_init_serialization(netplay_t *netplay);
/** /**
* netplay_wait_and_init_serialization * netplay_wait_and_init_serialization
* *
@ -772,59 +760,10 @@ bool netplay_try_init_serialization(netplay_t *netplay);
*/ */
bool netplay_wait_and_init_serialization(netplay_t *netplay); bool netplay_wait_and_init_serialization(netplay_t *netplay);
/**
* netplay_new:
* @server : IP address of server.
* @mitm : IP address of the MITM/tunnel server.
* @port : Port of server.
* @mitm_session : Session id for MITM/tunnel.
* @stateless_mode : Shall we run in stateless mode?
* @check_frames : Frequency with which to check CRCs.
* @cb : Libretro callbacks.
* @nat_traversal : If true, attempt NAT traversal.
* @nick : Nickname of user.
* @quirks : Netplay quirks required for this session.
*
* Creates a new netplay handle. A NULL server means we're
* hosting.
*
* Returns: new netplay data.
*/
netplay_t *netplay_new(const char *server, const char *mitm, uint16_t port,
const char *mitm_session,
bool stateless_mode, int check_frames,
const struct retro_callbacks *cb,
bool nat_traversal, const char *nick,
uint64_t quirks);
/**
* netplay_free
* @netplay : pointer to netplay object
*
* Frees netplay data/
*/
void netplay_free(netplay_t *netplay);
/*************************************************************** /***************************************************************
* NETPLAY-IO.C * NETPLAY-IO.C
**************************************************************/ **************************************************************/
/**
* netplay_hangup:
*
* Disconnects an active Netplay connection due to an error
*/
void netplay_hangup(netplay_t *netplay,
struct netplay_connection *connection);
/**
* netplay_delayed_state_change:
*
* Handle any pending state changes which are ready as
* of the beginning of the current frame.
*/
void netplay_delayed_state_change(netplay_t *netplay);
/** /**
* netplay_send_cur_input * netplay_send_cur_input
* *
@ -866,85 +805,20 @@ void netplay_send_raw_cmd_all(netplay_t *netplay,
bool netplay_cmd_mode(netplay_t *netplay, bool netplay_cmd_mode(netplay_t *netplay,
enum rarch_netplay_connection_mode mode); enum rarch_netplay_connection_mode mode);
/**
* netplay_poll_net_input
*
* Poll input from the network
*/
int netplay_poll_net_input(netplay_t *netplay, bool block);
/**
* netplay_handle_slaves
*
* Handle any slave connections
*/
void netplay_handle_slaves(netplay_t *netplay);
/**
* netplay_init_nat_traversal
*
* Initialize the NAT traversal library and try to open a port
*/
void netplay_init_nat_traversal(netplay_t *netplay);
void netplay_deinit_nat_traversal(void);
/***************************************************************
* NETPLAY-KEYBOARD.C
**************************************************************/
/* The mapping of keys from libretro (host) to netplay (network) */
uint32_t netplay_key_hton(unsigned key);
/* Because the hton keymapping has to be generated,
* call this before using netplay_key_hton */
void netplay_key_hton_init(void);
/*************************************************************** /***************************************************************
* NETPLAY-SYNC.C * NETPLAY-SYNC.C
**************************************************************/ **************************************************************/
/** /**
* netplay_update_unread_ptr * netplay_load_savestate
*
* Update the global unread_ptr and unread_frame_count to
* correspond to the earliest unread frame count of any
* connected player.
*/
void netplay_update_unread_ptr(netplay_t *netplay);
/**
* netplay_resolve_input
* @netplay : pointer to netplay object
* @sim_ptr : frame pointer for which to resolve input
* @resim : are we resimulating, or simulating this
* frame for the first time?
*
* "Simulate" input by assuming it hasn't changed since the
* last read input.
* Returns true if the resolved input changed from the
* last time it was resolved.
*/
bool netplay_resolve_input(netplay_t *netplay,
size_t sim_ptr, bool resim);
/**
* netplay_sync_pre_frame
* @netplay : pointer to netplay object * @netplay : pointer to netplay object
* @disconnect : disconnect netplay * @serial_info : the savestate being loaded, NULL means
* "load it yourself"
* @save : Whether to save the provided serial_info
* into the frame buffer
* *
* Pre-frame for Netplay synchronization. * Inform Netplay of a savestate load and send it to the other side
*/ **/
bool netplay_sync_pre_frame(netplay_t *netplay, bool *disconnect); void netplay_load_savestate(netplay_t *netplay,
retro_ctx_serialize_info_t *serial_info, bool save);
/**
* netplay_sync_post_frame
* @netplay : pointer to netplay object
* @stalled : true if we're currently stalled
*
* Post-frame for Netplay synchronization.
* We check if we have new input and replay from recorded input.
*/
void netplay_sync_post_frame(netplay_t *netplay, bool stalled);
#endif #endif