diff --git a/include/rc_api_info.h b/include/rc_api_info.h index 0051fe23..1894cce1 100644 --- a/include/rc_api_info.h +++ b/include/rc_api_info.h @@ -164,8 +164,26 @@ rc_api_fetch_games_list_request_t; typedef struct rc_api_game_list_entry_t { /* The unique identifier of the game */ uint32_t id; + /* The number of achievements in the game */ + uint32_t num_achievements; + /* The number of leaderboards in the game */ + uint32_t num_leaderboards; + /* The number of points in the game */ + uint32_t points; /* The name of the game */ const char* name; + /* The image name for the game badge */ + const char* image_name; + /* The URL for the game badge image */ + const char* image_url; + /* An array of supported hashes */ + const char** supported_hashes; + /* An array of unsupported hashes */ + const char** unsupported_hashes; + /* The number of items in the supported_hashes array */ + uint32_t num_supported_hashes; + /* The number of items in the unsupported_hashes array */ + uint32_t num_unsupported_hashes; } rc_api_game_list_entry_t; diff --git a/src/rapi/rc_api_common.c b/src/rapi/rc_api_common.c index 39b12359..063d6ee5 100644 --- a/src/rapi/rc_api_common.c +++ b/src/rapi/rc_api_common.c @@ -503,6 +503,57 @@ int rc_json_get_required_unum_array(uint32_t** entries, uint32_t* num_entries, r return RC_OK; } +static int rc_json_get_string_array(const char*** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* array, const char* field_name) { + if (*num_entries) { + rc_json_iterator_t iterator; + rc_json_field_t value; + const char** entry; + + *entries = (const char**)rc_buffer_alloc(&response->buffer, *num_entries * sizeof(const char*)); + if (!*entries) + return RC_OUT_OF_MEMORY; + + value.name = field_name; + + memset(&iterator, 0, sizeof(iterator)); + iterator.json = array->value_start; + iterator.end = array->value_end; + + entry = *entries; + while (rc_json_get_array_entry_value(&value, &iterator)) { + if (!rc_json_get_string(entry, &response->buffer, &value, field_name)) + return RC_MISSING_VALUE; + + ++entry; + } + } + else { + *entries = NULL; + } + + return RC_OK; +} + +int rc_json_get_required_string_array(const char*** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { + rc_json_field_t array; + + memset(&array, 0, sizeof(array)); + if (!rc_json_get_required_array(num_entries, &array, response, field, field_name)) + return RC_MISSING_VALUE; + + return rc_json_get_string_array(entries, num_entries, response, &array, field_name); +} + +int rc_json_get_optional_string_array(const char*** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { + rc_json_field_t array; + + memset(&array, 0, sizeof(array)); + if (!rc_json_get_optional_array(num_entries, &array, field, field_name)) + *num_entries = 0; + + return rc_json_get_string_array(entries, num_entries, response, &array, field_name); +} + int rc_json_get_required_array(uint32_t* num_entries, rc_json_field_t* array_field, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) diff --git a/src/rapi/rc_api_common.h b/src/rapi/rc_api_common.h index 78e10d49..c3351340 100644 --- a/src/rapi/rc_api_common.h +++ b/src/rapi/rc_api_common.h @@ -64,6 +64,8 @@ int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_js int rc_json_get_required_datetime(time_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); int rc_json_get_required_object(rc_json_field_t* fields, size_t field_count, rc_api_response_t* response, rc_json_field_t* field, const char* field_name); int rc_json_get_required_unum_array(uint32_t** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); +int rc_json_get_required_string_array(const char*** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); +int rc_json_get_optional_string_array(const char*** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); int rc_json_get_required_array(uint32_t* num_entries, rc_json_field_t* array_field, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_iterator_t* iterator); int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t* field); diff --git a/src/rapi/rc_api_info.c b/src/rapi/rc_api_info.c index 5ab97092..4d19a0e8 100644 --- a/src/rapi/rc_api_info.c +++ b/src/rapi/rc_api_info.c @@ -325,8 +325,8 @@ int rc_api_init_fetch_games_list_request_hosted(rc_api_request_t* request, return RC_INVALID_STATE; rc_url_builder_init(&builder, &request->buffer, 48); - rc_url_builder_append_str_param(&builder, "r", "gameslist"); - rc_url_builder_append_unum_param(&builder, "c", api_params->console_id); + rc_url_builder_append_str_param(&builder, "r", "systemgames"); + rc_url_builder_append_unum_param(&builder, "s", api_params->console_id); request->post_data = rc_url_builder_finalize(&builder); request->content_type = RC_CONTENT_TYPE_URLENCODED; @@ -347,9 +347,8 @@ int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response) { rc_api_game_list_entry_t* entry; rc_json_iterator_t iterator; - rc_json_field_t field; + rc_json_field_t array_field; int result; - char* end; rc_json_field_t fields[] = { RC_JSON_NEW_FIELD("Success"), @@ -357,6 +356,18 @@ int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_resp RC_JSON_NEW_FIELD("Response") }; + rc_json_field_t game_fields[] = { + RC_JSON_NEW_FIELD("ID"), + RC_JSON_NEW_FIELD("Title"), + RC_JSON_NEW_FIELD("ImageIcon"), + RC_JSON_NEW_FIELD("ImageUrl"), + RC_JSON_NEW_FIELD("NumAchievements"), + RC_JSON_NEW_FIELD("NumLeaderboards"), + RC_JSON_NEW_FIELD("Points"), + RC_JSON_NEW_FIELD("SupportedHashes"), /* array */ + RC_JSON_NEW_FIELD("UnsupportedHashes"), /* array */ + }; + memset(response, 0, sizeof(*response)); rc_buffer_init(&response->response.buffer); @@ -364,32 +375,50 @@ int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_resp if (result != RC_OK) return result; - if (!fields[2].value_start) { - /* call rc_json_get_required_object to generate the error message */ - rc_json_get_required_object(NULL, 0, &response->response, &fields[2], "Response"); + if (!rc_json_get_required_array(&response->num_entries, &array_field, &response->response, &fields[2], "Response")) return RC_MISSING_VALUE; - } - response->num_entries = fields[2].array_size; - rc_buffer_reserve(&response->response.buffer, response->num_entries * (32 + sizeof(rc_api_game_list_entry_t))); + if (response->num_entries) { + /* 8=image_name, 32=title, 64=image_url, 32=one hash */ + rc_buffer_reserve(&response->response.buffer, response->num_entries * (8 + 32 + 64 + 32 + sizeof(rc_api_game_list_entry_t))); - response->entries = (rc_api_game_list_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_game_list_entry_t)); - if (!response->entries) - return RC_OUT_OF_MEMORY; + response->entries = (rc_api_game_list_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_game_list_entry_t)); + if (!response->entries) + return RC_OUT_OF_MEMORY; - memset(&iterator, 0, sizeof(iterator)); - iterator.json = fields[2].value_start; - iterator.end = fields[2].value_end; + memset(&iterator, 0, sizeof(iterator)); + iterator.json = array_field.value_start; + iterator.end = array_field.value_end; - entry = response->entries; - while (rc_json_get_next_object_field(&iterator, &field)) { - entry->id = strtol(field.name, &end, 10); + entry = response->entries; + while (rc_json_get_array_entry_object(game_fields, sizeof(game_fields) / sizeof(game_fields[0]), &iterator)) { + if (!rc_json_get_required_unum(&entry->id, &response->response, &game_fields[0], "ID")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_string(&entry->name, &response->response, &game_fields[1], "Title")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_unum(&entry->num_achievements, &response->response, &game_fields[4], "NumAchievements")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_unum(&entry->num_leaderboards, &response->response, &game_fields[5], "NumLeaderboards")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_unum(&entry->points, &response->response, &game_fields[6], "Points")) + return RC_MISSING_VALUE; - field.name = ""; - if (!rc_json_get_string(&entry->name, &response->response.buffer, &field, "")) - return RC_MISSING_VALUE; + /* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */ + rc_json_extract_filename(&game_fields[2]); + if (!rc_json_get_required_string(&entry->image_name, &response->response, &game_fields[2], "ImageIcon")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_string(&entry->image_url, &response->response, &game_fields[3], "ImageUrl")) + return RC_MISSING_VALUE; - ++entry; + result = rc_json_get_required_string_array(&entry->supported_hashes, &entry->num_supported_hashes, &response->response, &game_fields[7], "SupportedHashes"); + if (result != RC_OK) + return result; + result = rc_json_get_optional_string_array(&entry->unsupported_hashes, &entry->num_unsupported_hashes, &response->response, &game_fields[8], "UnsupportedHashes"); + if (result != RC_OK) + return result; + + ++entry; + } } return RC_OK; diff --git a/test/rapi/test_rc_api_info.c b/test/rapi/test_rc_api_info.c index def8773e..659793e1 100644 --- a/test/rapi/test_rc_api_info.c +++ b/test/rapi/test_rc_api_info.c @@ -324,7 +324,7 @@ static void test_init_fetch_games_list_request() { ASSERT_NUM_EQUALS(rc_api_init_fetch_games_list_request(&request, &fetch_games_list_request), RC_OK); ASSERT_STR_EQUALS(request.url, DOREQUEST_URL); - ASSERT_STR_EQUALS(request.post_data, "r=gameslist&c=12"); + ASSERT_STR_EQUALS(request.post_data, "r=systemgames&s=12"); ASSERT_STR_EQUALS(request.content_type, RC_CONTENT_TYPE_URLENCODED); rc_api_destroy_request(&request); @@ -333,40 +333,65 @@ static void test_init_fetch_games_list_request() { static void test_process_fetch_games_list_response() { rc_api_fetch_games_list_response_t fetch_games_list_response; rc_api_game_list_entry_t* entry; - const char* server_response = "{\"Success\":true,\"Response\":{" - "\"1234\":\"Game Name 1\"," - "\"17\":\"Game Name 2\"," - "\"9923\":\"Game Name 3\"," - "\"12303\":\"Game Name 4\"," - "\"4338\":\"Game Name 5\"," - "\"5437\":\"Game Name 6\"" - "}}"; + const char* server_response = "{\"Success\":true,\"Response\":[" + "{\"ID\":111,\"Title\":\"Game Name 1\",\"NumAchievements\":6,\"NumLeaderboards\":0,\"Points\":40," + "\"ImageIcon\":\"/Images/001110.png\",\"ImageUrl\":\"http://host/Images/001110.png\"," + "\"SupportedHashes\":[\"0123456789abcdeffedcba9876543210\"]}," + "{\"ID\":222,\"Title\":\"Game Name 2\",\"NumAchievements\":0,\"NumLeaderboards\":0,\"Points\":0," + "\"ImageIcon\":\"/Images/002220.png\",\"ImageUrl\":\"http://host/Images/002220.png\"," + "\"SupportedHashes\":[]}," + "{\"ID\":333,\"Title\":\"Game Name 3\",\"NumAchievements\":14,\"NumLeaderboards\":3,\"Points\":200," + "\"ImageIcon\":\"/Images/003330.png\",\"ImageUrl\":\"http://host/Images/003330.png\"," + "\"SupportedHashes\":[\"deadbeefdeadbeefdeadbeefdeadbeef\",\"00112233445566778899aabbccddeeff\"]," + "\"UnsupportedHashes\":[\"abababababababababababababababab\"]}" + "]}"; memset(&fetch_games_list_response, 0, sizeof(fetch_games_list_response)); ASSERT_NUM_EQUALS(rc_api_process_fetch_games_list_response(&fetch_games_list_response, server_response), RC_OK); ASSERT_NUM_EQUALS(fetch_games_list_response.response.succeeded, 1); ASSERT_PTR_NULL(fetch_games_list_response.response.error_message); - ASSERT_NUM_EQUALS(fetch_games_list_response.num_entries, 6); + ASSERT_NUM_EQUALS(fetch_games_list_response.num_entries, 3); entry = &fetch_games_list_response.entries[0]; - ASSERT_NUM_EQUALS(entry->id, 1234); + ASSERT_NUM_EQUALS(entry->id, 111); ASSERT_STR_EQUALS(entry->name, "Game Name 1"); + ASSERT_NUM_EQUALS(entry->num_achievements, 6); + ASSERT_NUM_EQUALS(entry->num_leaderboards, 0); + ASSERT_NUM_EQUALS(entry->points, 40); + ASSERT_STR_EQUALS(entry->image_name, "001110"); + ASSERT_STR_EQUALS(entry->image_url, "http://host/Images/001110.png"); + ASSERT_NUM_EQUALS(entry->num_supported_hashes, 1); + ASSERT_STR_EQUALS(entry->supported_hashes[0], "0123456789abcdeffedcba9876543210"); + ASSERT_NUM_EQUALS(entry->num_unsupported_hashes, 0); + ASSERT_PTR_NULL(entry->unsupported_hashes); + entry = &fetch_games_list_response.entries[1]; - ASSERT_NUM_EQUALS(entry->id, 17); + ASSERT_NUM_EQUALS(entry->id, 222); ASSERT_STR_EQUALS(entry->name, "Game Name 2"); + ASSERT_NUM_EQUALS(entry->num_achievements, 0); + ASSERT_NUM_EQUALS(entry->num_leaderboards, 0); + ASSERT_NUM_EQUALS(entry->points, 0); + ASSERT_STR_EQUALS(entry->image_name, "002220"); + ASSERT_STR_EQUALS(entry->image_url, "http://host/Images/002220.png"); + ASSERT_NUM_EQUALS(entry->num_supported_hashes, 0); + ASSERT_PTR_NULL(entry->supported_hashes); + ASSERT_NUM_EQUALS(entry->num_unsupported_hashes, 0); + ASSERT_PTR_NULL(entry->unsupported_hashes); + entry = &fetch_games_list_response.entries[2]; - ASSERT_NUM_EQUALS(entry->id, 9923); + ASSERT_NUM_EQUALS(entry->id, 333); ASSERT_STR_EQUALS(entry->name, "Game Name 3"); - entry = &fetch_games_list_response.entries[3]; - ASSERT_NUM_EQUALS(entry->id, 12303); - ASSERT_STR_EQUALS(entry->name, "Game Name 4"); - entry = &fetch_games_list_response.entries[4]; - ASSERT_NUM_EQUALS(entry->id, 4338); - ASSERT_STR_EQUALS(entry->name, "Game Name 5"); - entry = &fetch_games_list_response.entries[5]; - ASSERT_NUM_EQUALS(entry->id, 5437); - ASSERT_STR_EQUALS(entry->name, "Game Name 6"); + ASSERT_NUM_EQUALS(entry->num_achievements, 14); + ASSERT_NUM_EQUALS(entry->num_leaderboards, 3); + ASSERT_NUM_EQUALS(entry->points, 200); + ASSERT_STR_EQUALS(entry->image_name, "003330"); + ASSERT_STR_EQUALS(entry->image_url, "http://host/Images/003330.png"); + ASSERT_NUM_EQUALS(entry->num_supported_hashes, 2); + ASSERT_STR_EQUALS(entry->supported_hashes[0], "deadbeefdeadbeefdeadbeefdeadbeef"); + ASSERT_STR_EQUALS(entry->supported_hashes[1], "00112233445566778899aabbccddeeff"); + ASSERT_NUM_EQUALS(entry->num_unsupported_hashes, 1); + ASSERT_STR_EQUALS(entry->unsupported_hashes[0], "abababababababababababababababab"); rc_api_destroy_fetch_games_list_response(&fetch_games_list_response); }