diff --git a/Makefile.common b/Makefile.common index 1c433f5c04..16964a7af7 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1195,7 +1195,8 @@ ifeq ($(HAVE_NETWORKING), 1) network/netplay/netplay_io.o \ network/netplay/netplay_sync.o \ network/netplay/netplay_discovery.o \ - network/netplay/netplay_buf.o + network/netplay/netplay_buf.o \ + network/netplay/netplay_room_parse.o # Retro Achievements (also depends on threads) diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index fd812b6403..24ba225ce9 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -3418,7 +3418,6 @@ finish: int j = 0; int k = 0; int lan_room_count = 0; - static struct string_list *room_data = NULL; struct netplay_host_list *lan_hosts = NULL; file_list_t *file_list = menu_entries_get_selection_buf_ptr(0); @@ -3428,7 +3427,7 @@ finish: if (lan_hosts) lan_room_count = lan_hosts->size; - room_data = string_split(buf, "\n"); + netplay_rooms_parse(buf); if (netplay_room_list) free(netplay_room_list); @@ -3437,20 +3436,11 @@ finish: * in the same list. If both entries are available, we want to show only * the LAN one. */ - netplay_room_count = (int)(room_data->size / 8); + netplay_room_count = netplay_rooms_get_count(); netplay_room_list = (struct netplay_room*) calloc(netplay_room_count + lan_room_count, sizeof(struct netplay_room)); -#if 0 - for (int i = 0; i < room_data->size; i++) - { - strlcpy(tmp, - room_data->elems[i].data, sizeof(tmp)); - RARCH_LOG("tmp %s\n", tmp); - - } -#endif menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, file_list); menu_entries_append_enum(file_list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_ENABLE_HOST), @@ -3469,7 +3459,9 @@ finish: for (i = 0; i < netplay_room_count; i++) { - strlcpy(netplay_room_list[i].nickname, + memcpy(&netplay_room_list[i], netplay_room_get(i), sizeof(netplay_room_list[i])); + + /*strlcpy(netplay_room_list[i].nickname, room_data->elems[j + 0].data, sizeof(netplay_room_list[i].nickname)); strlcpy(netplay_room_list[i].address, @@ -3487,7 +3479,7 @@ finish: netplay_room_list[i].port = atoi(room_data->elems[j + 2].data); netplay_room_list[i].gamecrc = atoi(room_data->elems[j + 6].data); - netplay_room_list[i].timestamp = atoi(room_data->elems[j + 7].data); + netplay_room_list[i].timestamp = atoi(room_data->elems[j + 7].data);*/ /* Uncomment this to debug mismatched room parameters*/ #if 0 @@ -3521,6 +3513,8 @@ finish: MENU_ENUM_LABEL_CONNECT_NETPLAY_ROOM, MENU_ROOM, 0, 0); } + + netplay_rooms_free(); } if (lan_room_count != 0) @@ -3587,7 +3581,7 @@ finish: static int action_ok_push_netplay_refresh_rooms(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) { - char url [2048] = "http://lobby.libretro.com/raw/"; + char url [2048] = "http://newlobby.libretro.com/list/"; task_push_netplay_lan_scan(); task_push_http_transfer(url, true, NULL, netplay_refresh_rooms_cb, NULL); return 0; diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 8b72bfc50b..8a22bc12dd 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -79,4 +79,12 @@ void deinit_netplay(void); bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data); +int netplay_rooms_parse(const char *buf); + +struct netplay_room* netplay_room_get(int index); + +int netplay_rooms_get_count(); + +void netplay_rooms_free(); + #endif diff --git a/network/netplay/netplay_discovery.h b/network/netplay/netplay_discovery.h index 68119cd020..bc177b01a7 100644 --- a/network/netplay/netplay_discovery.h +++ b/network/netplay/netplay_discovery.h @@ -60,6 +60,10 @@ struct netplay_room char gamename [PATH_MAX_LENGTH]; int gamecrc; int timestamp; + bool has_password; + bool has_spectate_password; + bool fixed; + struct netplay_room *next; }; extern struct netplay_room *netplay_room_list; diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index a28736874b..3136e6ba2b 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -521,7 +521,7 @@ static void netplay_announce_cb(void *task_data, void *user_data, const char *er static void netplay_announce(void) { char buf [2048]; - char url [2048] = "http://lobby.libretro.com/raw/?"; + char url [2048] = "http://newlobby.libretro.com/add/"; rarch_system_info_t *system = NULL; settings_t *settings = config_get_ptr(); uint32_t *content_crc_ptr = NULL; @@ -532,14 +532,13 @@ static void netplay_announce(void) buf[0] = '\0'; - snprintf(buf, sizeof(buf), "%susername=%s&corename=%s&coreversion=%s&" - "gamename=%s&gamecrc=%d&port=%d", - url, settings->username, system->info.library_name, - system->info.library_version, - !string_is_empty(path_basename(path_get(RARCH_PATH_BASENAME))) ? path_basename(path_get(RARCH_PATH_BASENAME)) : "N/A",*content_crc_ptr, - settings->netplay.port); + snprintf(buf, sizeof(buf), "username=%s&core_name=%s&core_version=%s&" + "game_name=%s&game_crc=%d&port=%d&has_password=%d&has_spectate_password=%d", + settings->username, system->info.library_name, system->info.library_version, + !string_is_empty(path_basename(path_get(RARCH_PATH_BASENAME))) ? path_basename(path_get(RARCH_PATH_BASENAME)) : "N/A", *content_crc_ptr, + settings->netplay.port, settings->netplay.password ? 1 : 0, settings->netplay.spectate_password ? 1 : 0); - task_push_http_transfer(buf, true, NULL, netplay_announce_cb, NULL); + task_push_http_post_transfer(url, buf, true, NULL, netplay_announce_cb, NULL); } int16_t input_state_net(unsigned port, unsigned device, diff --git a/network/netplay/netplay_room_parse.c b/network/netplay/netplay_room_parse.c new file mode 100644 index 0000000000..9ae5565cd2 --- /dev/null +++ b/network/netplay/netplay_room_parse.c @@ -0,0 +1,466 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2017 - Daniel De Matteis + * Copyright (C) 2016-2017 - Gregor Richards + * Copyright (C) 2017 - Brad Parker + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include "netplay_discovery.h" +#include "../../verbosity.h" + +enum parse_state +{ + STATE_START = 0, + STATE_ARRAY_START, + STATE_OBJECT_START, + STATE_FIELDS_START, + STATE_FIELDS_OBJECT_START, + STATE_END +}; + +struct netplay_rooms +{ + struct netplay_room *head; + struct netplay_room *cur; +}; + +typedef struct tag_Context +{ + JSON_Parser parser; + int inEmptyContainer; + enum parse_state state; + char *cur_field; + void *cur_member; + size_t cur_member_size; +} Context; + +static struct netplay_rooms *rooms; + +static void parse_context_init(Context* pCtx) +{ + pCtx->parser = NULL; + pCtx->inEmptyContainer = 0; +} + +static void parse_context_free(Context* pCtx) +{ + if (pCtx->cur_field) + free(pCtx->cur_field); + + pCtx->cur_field = NULL; + + JSON_Parser_Free(pCtx->parser); +} + +static JSON_Parser_HandlerResult JSON_CALL EncodingDetectedHandler(JSON_Parser parser) +{ + (void)parser; + return JSON_Parser_Continue; +} + +static JSON_Parser_HandlerResult JSON_CALL NullHandler(JSON_Parser parser) +{ + Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); + (void)parser; + pCtx->inEmptyContainer = 0; + return JSON_Parser_Continue; +} + +static JSON_Parser_HandlerResult JSON_CALL BooleanHandler(JSON_Parser parser, JSON_Boolean value) +{ + Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); + (void)parser; + pCtx->inEmptyContainer = 0; + + if (pCtx->state == STATE_FIELDS_OBJECT_START) + { + RARCH_LOG("found boolean: %d\n", value); + + if (pCtx->cur_field) + *((bool*)pCtx->cur_member) = value; + } + + return JSON_Parser_Continue; +} + +static JSON_Parser_HandlerResult JSON_CALL StringHandler(JSON_Parser parser, char* pValue, size_t length, JSON_StringAttributes attributes) +{ + Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); + (void)parser; + (void)attributes; + pCtx->inEmptyContainer = 0; + + if (pCtx->state == STATE_FIELDS_OBJECT_START) + { + RARCH_LOG("found string: %s\n", pValue); + + if (pValue && length) + { + if (pCtx->cur_field && string_is_equal(pCtx->cur_field, "game_crc")) + { + /* CRC comes in as a string but it is stored as an int */ + *((int*)pCtx->cur_member) = strtoul(pValue, NULL, 16); + } + else if (pCtx->cur_field) + strlcpy((char*)pCtx->cur_member, pValue, PATH_MAX_LENGTH); + } + } + + return JSON_Parser_Continue; +} + +static JSON_Parser_HandlerResult JSON_CALL NumberHandler(JSON_Parser parser, char* pValue, size_t length, JSON_NumberAttributes attributes) +{ + Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); + (void)parser; + (void)attributes; + pCtx->inEmptyContainer = 0; + + if (pCtx->state == STATE_FIELDS_OBJECT_START) + { + RARCH_LOG("found number string: %s\n", pValue); + + if (pValue && length) + if (pCtx->cur_field) + *((int*)pCtx->cur_member) = strtol(pValue, NULL, 10); + } + + return JSON_Parser_Continue; +} + +static JSON_Parser_HandlerResult JSON_CALL SpecialNumberHandler(JSON_Parser parser, JSON_SpecialNumber value) +{ + Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); + (void)parser; + pCtx->inEmptyContainer = 0; + return JSON_Parser_Continue; +} + +static JSON_Parser_HandlerResult JSON_CALL StartObjectHandler(JSON_Parser parser) +{ + Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); + (void)parser; + pCtx->inEmptyContainer = 1; + RARCH_LOG("object start\n"); + + if (pCtx->state == STATE_FIELDS_START) + { + pCtx->state = STATE_FIELDS_OBJECT_START; + + if (!rooms->head) + { + rooms->head = (struct netplay_room*)calloc(1, sizeof(*rooms->head)); + rooms->cur = rooms->head; + } + else if (!rooms->cur->next) + { + rooms->cur->next = (struct netplay_room*)calloc(1, sizeof(*rooms->cur->next)); + rooms->cur = rooms->cur->next; + } + } + else if (pCtx->state == STATE_ARRAY_START) + pCtx->state = STATE_OBJECT_START; + + return JSON_Parser_Continue; +} + +static JSON_Parser_HandlerResult JSON_CALL EndObjectHandler(JSON_Parser parser) +{ + Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); + if (!pCtx->inEmptyContainer) + { + /* indent */ + } + pCtx->inEmptyContainer = 0; + RARCH_LOG("object end\n"); + + if (pCtx->state == STATE_FIELDS_OBJECT_START) + pCtx->state = STATE_ARRAY_START; + + return JSON_Parser_Continue; +} + +static JSON_Parser_HandlerResult JSON_CALL ObjectMemberHandler(JSON_Parser parser, char* pValue, size_t length, JSON_StringAttributes attributes) +{ + Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); + (void)parser; + (void)attributes; + if (!pCtx->inEmptyContainer) + RARCH_LOG("object member comma\n"); + RARCH_LOG("object member %I64u: %s\n", length, pValue); + + if (!pValue || !length) + return JSON_Parser_Continue; + + if (pCtx->state == STATE_OBJECT_START && string_is_equal(pValue, "fields")) + pCtx->state = STATE_FIELDS_START; + + if (pCtx->state == STATE_FIELDS_OBJECT_START) + { + RARCH_LOG("got field %s\n", pValue); + + if (pCtx->cur_field) + free(pCtx->cur_field); + + pCtx->cur_field = strdup(pValue); + + if (string_is_equal(pValue, "username")) + { + pCtx->cur_member = &rooms->cur->nickname; + pCtx->cur_member_size = sizeof(rooms->cur->nickname); + } + else if (string_is_equal(pValue, "game_name")) + { + pCtx->cur_member = &rooms->cur->gamename; + pCtx->cur_member_size = sizeof(rooms->cur->gamename); + } + else if (string_is_equal(pValue, "core_name")) + { + pCtx->cur_member = &rooms->cur->corename; + pCtx->cur_member_size = sizeof(rooms->cur->corename); + } + else if (string_is_equal(pValue, "ip")) + { + pCtx->cur_member = &rooms->cur->address; + pCtx->cur_member_size = sizeof(rooms->cur->address); + } + else if (string_is_equal(pValue, "port")) + pCtx->cur_member = &rooms->cur->port; + else if (string_is_equal(pValue, "game_crc")) + pCtx->cur_member = &rooms->cur->gamecrc; + else if (string_is_equal(pValue, "core_version")) + { + pCtx->cur_member = &rooms->cur->coreversion; + pCtx->cur_member_size = sizeof(rooms->cur->coreversion); + } + else if (string_is_equal(pValue, "has_password")) + pCtx->cur_member = &rooms->cur->has_password; + else if (string_is_equal(pValue, "has_spectate_password")) + pCtx->cur_member = &rooms->cur->has_spectate_password; + else if (string_is_equal(pValue, "fixed")) + pCtx->cur_member = &rooms->cur->fixed; + else + { + /* unknown field, ignore it */ + free(pCtx->cur_field); + pCtx->cur_field = NULL; + } + } + + pCtx->inEmptyContainer = 0; + /* indent, string, space, colon, space */ + return JSON_Parser_Continue; +} + +static JSON_Parser_HandlerResult JSON_CALL StartArrayHandler(JSON_Parser parser) +{ + Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); + (void)parser; + pCtx->inEmptyContainer = 1; + RARCH_LOG("array start\n"); + + if (pCtx->state == STATE_START) + pCtx->state = STATE_ARRAY_START; + + return JSON_Parser_Continue; +} + +static JSON_Parser_HandlerResult JSON_CALL EndArrayHandler(JSON_Parser parser) +{ + Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); + (void)parser; + if (!pCtx->inEmptyContainer) + { + /* indent */ + } + pCtx->inEmptyContainer = 0; + RARCH_LOG("array end\n"); + return JSON_Parser_Continue; +} + +static JSON_Parser_HandlerResult JSON_CALL ArrayItemHandler(JSON_Parser parser) +{ + Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); + (void)parser; + if (!pCtx->inEmptyContainer) + { + RARCH_LOG("array comma\n"); + } + pCtx->inEmptyContainer = 0; + /* indent */ + return JSON_Parser_Continue; +} + +static int parse_context_setup(Context* pCtx) +{ + /*JSON_Parser_SetTrackObjectMembers(pCtx->parser, JSON_True); + JSON_Parser_SetAllowBOM(pCtx->parser, JSON_True); + JSON_Parser_SetAllowComments(pCtx->parser, JSON_True); + JSON_Parser_SetAllowSpecialNumbers(pCtx->parser, JSON_True); + JSON_Parser_SetAllowHexNumbers(pCtx->parser, JSON_True); + JSON_Parser_SetAllowUnescapedControlCharacters(pCtx->parser, JSON_True); + JSON_Parser_SetReplaceInvalidEncodingSequences(pCtx->parser, JSON_True); + JSON_Parser_SetTrackObjectMembers(pCtx->parser, JSON_False);*/ + + if (JSON_Parser_GetInputEncoding(pCtx->parser) == JSON_UnknownEncoding) + { + JSON_Parser_SetEncodingDetectedHandler(pCtx->parser, &EncodingDetectedHandler); + } + + JSON_Parser_SetNullHandler(pCtx->parser, &NullHandler); + JSON_Parser_SetBooleanHandler(pCtx->parser, &BooleanHandler); + JSON_Parser_SetStringHandler(pCtx->parser, &StringHandler); + JSON_Parser_SetNumberHandler(pCtx->parser, &NumberHandler); + JSON_Parser_SetSpecialNumberHandler(pCtx->parser, &SpecialNumberHandler); + JSON_Parser_SetStartObjectHandler(pCtx->parser, &StartObjectHandler); + JSON_Parser_SetEndObjectHandler(pCtx->parser, &EndObjectHandler); + JSON_Parser_SetObjectMemberHandler(pCtx->parser, &ObjectMemberHandler); + JSON_Parser_SetStartArrayHandler(pCtx->parser, &StartArrayHandler); + JSON_Parser_SetEndArrayHandler(pCtx->parser, &EndArrayHandler); + JSON_Parser_SetArrayItemHandler(pCtx->parser, &ArrayItemHandler); + JSON_Parser_SetUserData(pCtx->parser, pCtx); + + return 1; +} + +static void parse_context_error(Context* pCtx) +{ + if (JSON_Parser_GetError(pCtx->parser) != JSON_Error_AbortedByHandler) + { + JSON_Error error = JSON_Parser_GetError(pCtx->parser); + JSON_Location errorLocation = {0, 0, 0}; + (void)JSON_Parser_GetErrorLocation(pCtx->parser, &errorLocation); + RARCH_ERR("invalid JSON at line %d, column %d (input byte %d) - %s.\n", + (int)errorLocation.line + 1, + (int)errorLocation.column + 1, + (int)errorLocation.byte, + JSON_ErrorString(error)); + } +} + +static int json_parse(Context* pCtx, const char *buf) +{ + if (!JSON_Parser_Parse(pCtx->parser, buf, strlen(buf), JSON_True)) + { + parse_context_error(pCtx); + return 0; + } + + return 1; +} + +void netplay_rooms_free() +{ + if (rooms) + { + struct netplay_room *room = rooms->head; + + if (room) + { + while (room != NULL) + { + struct netplay_room *next = room->next; + + free(room); + + room = next; + } + + free(rooms); + } + else + free(rooms); + + rooms = NULL; + } +} + +int netplay_rooms_parse(const char *buf) +{ + Context ctx; + + memset(&ctx, 0, sizeof(ctx)); + + ctx.state = STATE_START; + + /* delete any previous rooms */ + netplay_rooms_free(); + + rooms = (struct netplay_rooms*)calloc(1, sizeof(*rooms)); + + parse_context_init(&ctx); + + ctx.parser = JSON_Parser_Create(NULL); + + if (!ctx.parser) + { + RARCH_ERR("could not allocate memory for JSON parser.\n"); + return 1; + } + + parse_context_setup(&ctx); + json_parse(&ctx, buf); + parse_context_free(&ctx); + + return 0; +} + +struct netplay_room* netplay_room_get(int index) +{ + int cur = 0; + struct netplay_room *room = rooms->head; + + if (index < 0) + return NULL; + + while (room != NULL) + { + if (cur == index) + break; + + room = room->next; + cur++; + } + + return room; +} + +int netplay_rooms_get_count() +{ + int count = 0; + struct netplay_room *room; + + if (!rooms) + return count; + + room = rooms->head; + + if (!room) + return count; + + while(room != NULL) + { + count++; + + room = room->next; + } + + return count; +}