rc_api_runtime.c (25613B)
1 #include "rc_api_runtime.h" 2 #include "rc_api_common.h" 3 4 #include "rc_runtime.h" 5 #include "rc_runtime_types.h" 6 #include "../rc_compat.h" 7 #include "../rhash/md5.h" 8 9 #include <stdlib.h> 10 #include <stdio.h> 11 #include <string.h> 12 13 /* --- Resolve Hash --- */ 14 15 int rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params) { 16 rc_api_url_builder_t builder; 17 18 rc_api_url_build_dorequest_url(request); 19 20 if (!api_params->game_hash || !*api_params->game_hash) 21 return RC_INVALID_STATE; 22 23 rc_url_builder_init(&builder, &request->buffer, 48); 24 rc_url_builder_append_str_param(&builder, "r", "gameid"); 25 rc_url_builder_append_str_param(&builder, "m", api_params->game_hash); 26 request->post_data = rc_url_builder_finalize(&builder); 27 request->content_type = RC_CONTENT_TYPE_URLENCODED; 28 29 return builder.result; 30 } 31 32 int rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response) { 33 rc_api_server_response_t response_obj; 34 35 memset(&response_obj, 0, sizeof(response_obj)); 36 response_obj.body = server_response; 37 response_obj.body_length = rc_json_get_object_string_length(server_response); 38 39 return rc_api_process_resolve_hash_server_response(response, &response_obj); 40 } 41 42 int rc_api_process_resolve_hash_server_response(rc_api_resolve_hash_response_t* response, const rc_api_server_response_t* server_response) { 43 int result; 44 rc_json_field_t fields[] = { 45 RC_JSON_NEW_FIELD("Success"), 46 RC_JSON_NEW_FIELD("Error"), 47 RC_JSON_NEW_FIELD("GameID") 48 }; 49 50 memset(response, 0, sizeof(*response)); 51 rc_buffer_init(&response->response.buffer); 52 53 result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); 54 if (result != RC_OK) 55 return result; 56 57 rc_json_get_required_unum(&response->game_id, &response->response, &fields[2], "GameID"); 58 return RC_OK; 59 } 60 61 void rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* response) { 62 rc_buffer_destroy(&response->response.buffer); 63 } 64 65 /* --- Fetch Game Data --- */ 66 67 int rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params) { 68 rc_api_url_builder_t builder; 69 70 rc_api_url_build_dorequest_url(request); 71 72 if (api_params->game_id == 0) 73 return RC_INVALID_STATE; 74 75 rc_url_builder_init(&builder, &request->buffer, 48); 76 if (rc_api_url_build_dorequest(&builder, "patch", api_params->username, api_params->api_token)) { 77 rc_url_builder_append_unum_param(&builder, "g", api_params->game_id); 78 request->post_data = rc_url_builder_finalize(&builder); 79 request->content_type = RC_CONTENT_TYPE_URLENCODED; 80 } 81 82 return builder.result; 83 } 84 85 int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response) { 86 rc_api_server_response_t response_obj; 87 88 memset(&response_obj, 0, sizeof(response_obj)); 89 response_obj.body = server_response; 90 response_obj.body_length = rc_json_get_object_string_length(server_response); 91 92 return rc_api_process_fetch_game_data_server_response(response, &response_obj); 93 } 94 95 static int rc_parse_achievement_type(const char* type) 96 { 97 if (strcmp(type, "missable") == 0) 98 return RC_ACHIEVEMENT_TYPE_MISSABLE; 99 100 if (strcmp(type, "win_condition") == 0) 101 return RC_ACHIEVEMENT_TYPE_WIN; 102 103 if (strcmp(type, "progression") == 0) 104 return RC_ACHIEVEMENT_TYPE_PROGRESSION; 105 106 return RC_ACHIEVEMENT_TYPE_STANDARD; 107 } 108 109 int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response) { 110 rc_api_achievement_definition_t* achievement; 111 rc_api_leaderboard_definition_t* leaderboard; 112 rc_json_field_t array_field; 113 rc_json_iterator_t iterator; 114 const char* last_author = ""; 115 const char* last_author_field = ""; 116 size_t last_author_len = 0; 117 size_t len; 118 uint32_t timet; 119 int result; 120 char format[16]; 121 122 rc_json_field_t fields[] = { 123 RC_JSON_NEW_FIELD("Success"), 124 RC_JSON_NEW_FIELD("Error"), 125 RC_JSON_NEW_FIELD("PatchData") /* nested object */ 126 }; 127 128 rc_json_field_t patchdata_fields[] = { 129 RC_JSON_NEW_FIELD("ID"), 130 RC_JSON_NEW_FIELD("Title"), 131 RC_JSON_NEW_FIELD("ConsoleID"), 132 RC_JSON_NEW_FIELD("ImageIcon"), 133 RC_JSON_NEW_FIELD("RichPresencePatch"), 134 RC_JSON_NEW_FIELD("Achievements"), /* array */ 135 RC_JSON_NEW_FIELD("Leaderboards") /* array */ 136 }; 137 138 rc_json_field_t achievement_fields[] = { 139 RC_JSON_NEW_FIELD("ID"), 140 RC_JSON_NEW_FIELD("Title"), 141 RC_JSON_NEW_FIELD("Description"), 142 RC_JSON_NEW_FIELD("Flags"), 143 RC_JSON_NEW_FIELD("Points"), 144 RC_JSON_NEW_FIELD("MemAddr"), 145 RC_JSON_NEW_FIELD("Author"), 146 RC_JSON_NEW_FIELD("BadgeName"), 147 RC_JSON_NEW_FIELD("Created"), 148 RC_JSON_NEW_FIELD("Modified"), 149 RC_JSON_NEW_FIELD("Type"), 150 RC_JSON_NEW_FIELD("Rarity"), 151 RC_JSON_NEW_FIELD("RarityHardcore") 152 }; 153 154 rc_json_field_t leaderboard_fields[] = { 155 RC_JSON_NEW_FIELD("ID"), 156 RC_JSON_NEW_FIELD("Title"), 157 RC_JSON_NEW_FIELD("Description"), 158 RC_JSON_NEW_FIELD("Mem"), 159 RC_JSON_NEW_FIELD("Format"), 160 RC_JSON_NEW_FIELD("LowerIsBetter"), 161 RC_JSON_NEW_FIELD("Hidden") 162 }; 163 164 memset(response, 0, sizeof(*response)); 165 rc_buffer_init(&response->response.buffer); 166 167 result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); 168 if (result != RC_OK || !response->response.succeeded) 169 return result; 170 171 if (!rc_json_get_required_object(patchdata_fields, sizeof(patchdata_fields) / sizeof(patchdata_fields[0]), &response->response, &fields[2], "PatchData")) 172 return RC_MISSING_VALUE; 173 174 if (!rc_json_get_required_unum(&response->id, &response->response, &patchdata_fields[0], "ID")) 175 return RC_MISSING_VALUE; 176 if (!rc_json_get_required_string(&response->title, &response->response, &patchdata_fields[1], "Title")) 177 return RC_MISSING_VALUE; 178 if (!rc_json_get_required_unum(&response->console_id, &response->response, &patchdata_fields[2], "ConsoleID")) 179 return RC_MISSING_VALUE; 180 181 /* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */ 182 rc_json_extract_filename(&patchdata_fields[3]); 183 rc_json_get_optional_string(&response->image_name, &response->response, &patchdata_fields[3], "ImageIcon", ""); 184 185 /* estimate the amount of space necessary to store the rich presence script, achievements, and leaderboards. 186 determine how much space each takes as a string in the JSON, then subtract out the non-data (field names, punctuation) 187 and add space for the structures. */ 188 len = patchdata_fields[4].value_end - patchdata_fields[4].value_start; /* rich presence */ 189 190 len += (patchdata_fields[5].value_end - patchdata_fields[5].value_start) - /* achievements */ 191 patchdata_fields[5].array_size * (130 - sizeof(rc_api_achievement_definition_t)); 192 193 len += (patchdata_fields[6].value_end - patchdata_fields[6].value_start) - /* leaderboards */ 194 patchdata_fields[6].array_size * (60 - sizeof(rc_api_leaderboard_definition_t)); 195 196 rc_buffer_reserve(&response->response.buffer, len); 197 /* end estimation */ 198 199 rc_json_get_optional_string(&response->rich_presence_script, &response->response, &patchdata_fields[4], "RichPresencePatch", ""); 200 if (!response->rich_presence_script) 201 response->rich_presence_script = ""; 202 203 if (!rc_json_get_required_array(&response->num_achievements, &array_field, &response->response, &patchdata_fields[5], "Achievements")) 204 return RC_MISSING_VALUE; 205 206 if (response->num_achievements) { 207 response->achievements = (rc_api_achievement_definition_t*)rc_buffer_alloc(&response->response.buffer, response->num_achievements * sizeof(rc_api_achievement_definition_t)); 208 if (!response->achievements) 209 return RC_OUT_OF_MEMORY; 210 211 memset(&iterator, 0, sizeof(iterator)); 212 iterator.json = array_field.value_start; 213 iterator.end = array_field.value_end; 214 215 achievement = response->achievements; 216 while (rc_json_get_array_entry_object(achievement_fields, sizeof(achievement_fields) / sizeof(achievement_fields[0]), &iterator)) { 217 if (!rc_json_get_required_unum(&achievement->id, &response->response, &achievement_fields[0], "ID")) 218 return RC_MISSING_VALUE; 219 if (!rc_json_get_required_string(&achievement->title, &response->response, &achievement_fields[1], "Title")) 220 return RC_MISSING_VALUE; 221 if (!rc_json_get_required_string(&achievement->description, &response->response, &achievement_fields[2], "Description")) 222 return RC_MISSING_VALUE; 223 if (!rc_json_get_required_unum(&achievement->category, &response->response, &achievement_fields[3], "Flags")) 224 return RC_MISSING_VALUE; 225 if (!rc_json_get_required_unum(&achievement->points, &response->response, &achievement_fields[4], "Points")) 226 return RC_MISSING_VALUE; 227 if (!rc_json_get_required_string(&achievement->definition, &response->response, &achievement_fields[5], "MemAddr")) 228 return RC_MISSING_VALUE; 229 if (!rc_json_get_required_string(&achievement->badge_name, &response->response, &achievement_fields[7], "BadgeName")) 230 return RC_MISSING_VALUE; 231 232 len = achievement_fields[6].value_end - achievement_fields[6].value_start; 233 if (len == last_author_len && memcmp(achievement_fields[6].value_start, last_author_field, len) == 0) { 234 achievement->author = last_author; 235 } 236 else { 237 if (!rc_json_get_required_string(&achievement->author, &response->response, &achievement_fields[6], "Author")) 238 return RC_MISSING_VALUE; 239 240 if (achievement->author == NULL) { 241 /* ensure we don't pass NULL out to client */ 242 last_author = achievement->author = ""; 243 last_author_len = 0; 244 } else { 245 last_author = achievement->author; 246 last_author_field = achievement_fields[6].value_start; 247 last_author_len = len; 248 } 249 } 250 251 if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[8], "Created")) 252 return RC_MISSING_VALUE; 253 achievement->created = (time_t)timet; 254 if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[9], "Modified")) 255 return RC_MISSING_VALUE; 256 achievement->updated = (time_t)timet; 257 258 achievement->type = RC_ACHIEVEMENT_TYPE_STANDARD; 259 if (achievement_fields[10].value_end) { 260 len = achievement_fields[10].value_end - achievement_fields[10].value_start - 2; 261 if (len < sizeof(format) - 1) { 262 memcpy(format, achievement_fields[10].value_start + 1, len); 263 format[len] = '\0'; 264 achievement->type = rc_parse_achievement_type(format); 265 } 266 } 267 268 /* legacy support : if title contains[m], change type to missable and remove[m] from title */ 269 if (memcmp(achievement->title, "[m]", 3) == 0) { 270 len = 3; 271 while (achievement->title[len] == ' ') 272 ++len; 273 achievement->title += len; 274 achievement->type = RC_ACHIEVEMENT_TYPE_MISSABLE; 275 } 276 else if (achievement_fields[1].value_end && memcmp(achievement_fields[1].value_end - 4, "[m]", 3) == 0) { 277 len = strlen(achievement->title) - 3; 278 while (achievement->title[len - 1] == ' ') 279 --len; 280 ((char*)achievement->title)[len] = '\0'; 281 achievement->type = RC_ACHIEVEMENT_TYPE_MISSABLE; 282 } 283 284 rc_json_get_optional_float(&achievement->rarity, &achievement_fields[11], "Rarity", 100.0); 285 rc_json_get_optional_float(&achievement->rarity_hardcore, &achievement_fields[12], "RarityHardcore", 100.0); 286 287 ++achievement; 288 } 289 } 290 291 if (!rc_json_get_required_array(&response->num_leaderboards, &array_field, &response->response, &patchdata_fields[6], "Leaderboards")) 292 return RC_MISSING_VALUE; 293 294 if (response->num_leaderboards) { 295 response->leaderboards = (rc_api_leaderboard_definition_t*)rc_buffer_alloc(&response->response.buffer, response->num_leaderboards * sizeof(rc_api_leaderboard_definition_t)); 296 if (!response->leaderboards) 297 return RC_OUT_OF_MEMORY; 298 299 memset(&iterator, 0, sizeof(iterator)); 300 iterator.json = array_field.value_start; 301 iterator.end = array_field.value_end; 302 303 leaderboard = response->leaderboards; 304 while (rc_json_get_array_entry_object(leaderboard_fields, sizeof(leaderboard_fields) / sizeof(leaderboard_fields[0]), &iterator)) { 305 if (!rc_json_get_required_unum(&leaderboard->id, &response->response, &leaderboard_fields[0], "ID")) 306 return RC_MISSING_VALUE; 307 if (!rc_json_get_required_string(&leaderboard->title, &response->response, &leaderboard_fields[1], "Title")) 308 return RC_MISSING_VALUE; 309 if (!rc_json_get_required_string(&leaderboard->description, &response->response, &leaderboard_fields[2], "Description")) 310 return RC_MISSING_VALUE; 311 if (!rc_json_get_required_string(&leaderboard->definition, &response->response, &leaderboard_fields[3], "Mem")) 312 return RC_MISSING_VALUE; 313 rc_json_get_optional_bool(&result, &leaderboard_fields[5], "LowerIsBetter", 0); 314 leaderboard->lower_is_better = (uint8_t)result; 315 rc_json_get_optional_bool(&result, &leaderboard_fields[6], "Hidden", 0); 316 leaderboard->hidden = (uint8_t)result; 317 318 if (!leaderboard_fields[4].value_end) 319 return RC_MISSING_VALUE; 320 len = leaderboard_fields[4].value_end - leaderboard_fields[4].value_start - 2; 321 if (len < sizeof(format) - 1) { 322 memcpy(format, leaderboard_fields[4].value_start + 1, len); 323 format[len] = '\0'; 324 leaderboard->format = rc_parse_format(format); 325 } 326 else { 327 leaderboard->format = RC_FORMAT_VALUE; 328 } 329 330 ++leaderboard; 331 } 332 } 333 334 return RC_OK; 335 } 336 337 void rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response) { 338 rc_buffer_destroy(&response->response.buffer); 339 } 340 341 /* --- Ping --- */ 342 343 int rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params) { 344 rc_api_url_builder_t builder; 345 346 rc_api_url_build_dorequest_url(request); 347 348 if (api_params->game_id == 0) 349 return RC_INVALID_STATE; 350 351 rc_url_builder_init(&builder, &request->buffer, 48); 352 if (rc_api_url_build_dorequest(&builder, "ping", api_params->username, api_params->api_token)) { 353 rc_url_builder_append_unum_param(&builder, "g", api_params->game_id); 354 355 if (api_params->rich_presence && *api_params->rich_presence) 356 rc_url_builder_append_str_param(&builder, "m", api_params->rich_presence); 357 358 if (api_params->game_hash && *api_params->game_hash) { 359 rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore); 360 rc_url_builder_append_str_param(&builder, "x", api_params->game_hash); 361 } 362 363 request->post_data = rc_url_builder_finalize(&builder); 364 request->content_type = RC_CONTENT_TYPE_URLENCODED; 365 } 366 367 return builder.result; 368 } 369 370 int rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response) { 371 rc_api_server_response_t response_obj; 372 373 memset(&response_obj, 0, sizeof(response_obj)); 374 response_obj.body = server_response; 375 response_obj.body_length = rc_json_get_object_string_length(server_response); 376 377 return rc_api_process_ping_server_response(response, &response_obj); 378 } 379 380 int rc_api_process_ping_server_response(rc_api_ping_response_t* response, const rc_api_server_response_t* server_response) { 381 rc_json_field_t fields[] = { 382 RC_JSON_NEW_FIELD("Success"), 383 RC_JSON_NEW_FIELD("Error") 384 }; 385 386 memset(response, 0, sizeof(*response)); 387 rc_buffer_init(&response->response.buffer); 388 389 return rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); 390 } 391 392 void rc_api_destroy_ping_response(rc_api_ping_response_t* response) { 393 rc_buffer_destroy(&response->response.buffer); 394 } 395 396 /* --- Award Achievement --- */ 397 398 int rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params) { 399 rc_api_url_builder_t builder; 400 char buffer[33]; 401 md5_state_t md5; 402 md5_byte_t digest[16]; 403 404 rc_api_url_build_dorequest_url(request); 405 406 if (api_params->achievement_id == 0) 407 return RC_INVALID_STATE; 408 409 rc_url_builder_init(&builder, &request->buffer, 96); 410 if (rc_api_url_build_dorequest(&builder, "awardachievement", api_params->username, api_params->api_token)) { 411 rc_url_builder_append_unum_param(&builder, "a", api_params->achievement_id); 412 rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore ? 1 : 0); 413 if (api_params->game_hash && *api_params->game_hash) 414 rc_url_builder_append_str_param(&builder, "m", api_params->game_hash); 415 416 /* Evaluate the signature. */ 417 md5_init(&md5); 418 snprintf(buffer, sizeof(buffer), "%u", api_params->achievement_id); 419 md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer)); 420 md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username)); 421 snprintf(buffer, sizeof(buffer), "%d", api_params->hardcore ? 1 : 0); 422 md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer)); 423 md5_finish(&md5, digest); 424 rc_format_md5(buffer, digest); 425 rc_url_builder_append_str_param(&builder, "v", buffer); 426 427 request->post_data = rc_url_builder_finalize(&builder); 428 request->content_type = RC_CONTENT_TYPE_URLENCODED; 429 } 430 431 return builder.result; 432 } 433 434 int rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response) { 435 rc_api_server_response_t response_obj; 436 437 memset(&response_obj, 0, sizeof(response_obj)); 438 response_obj.body = server_response; 439 response_obj.body_length = rc_json_get_object_string_length(server_response); 440 441 return rc_api_process_award_achievement_server_response(response, &response_obj); 442 } 443 444 int rc_api_process_award_achievement_server_response(rc_api_award_achievement_response_t* response, const rc_api_server_response_t* server_response) { 445 int result; 446 rc_json_field_t fields[] = { 447 RC_JSON_NEW_FIELD("Success"), 448 RC_JSON_NEW_FIELD("Error"), 449 RC_JSON_NEW_FIELD("Score"), 450 RC_JSON_NEW_FIELD("SoftcoreScore"), 451 RC_JSON_NEW_FIELD("AchievementID"), 452 RC_JSON_NEW_FIELD("AchievementsRemaining") 453 }; 454 455 memset(response, 0, sizeof(*response)); 456 rc_buffer_init(&response->response.buffer); 457 458 result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); 459 if (result != RC_OK) 460 return result; 461 462 if (!response->response.succeeded) { 463 if (response->response.error_message && 464 memcmp(response->response.error_message, "User already has", 16) == 0) { 465 /* not really an error, the achievement is unlocked, just not by the current call. 466 * hardcore: User already has hardcore and regular achievements awarded. 467 * non-hardcore: User already has this achievement awarded. 468 */ 469 response->response.succeeded = 1; 470 } else { 471 return result; 472 } 473 } 474 475 rc_json_get_optional_unum(&response->new_player_score, &fields[2], "Score", 0); 476 rc_json_get_optional_unum(&response->new_player_score_softcore, &fields[3], "SoftcoreScore", 0); 477 rc_json_get_optional_unum(&response->awarded_achievement_id, &fields[4], "AchievementID", 0); 478 rc_json_get_optional_unum(&response->achievements_remaining, &fields[5], "AchievementsRemaining", (unsigned)-1); 479 480 return RC_OK; 481 } 482 483 void rc_api_destroy_award_achievement_response(rc_api_award_achievement_response_t* response) { 484 rc_buffer_destroy(&response->response.buffer); 485 } 486 487 /* --- Submit Leaderboard Entry --- */ 488 489 int rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params) { 490 rc_api_url_builder_t builder; 491 char buffer[33]; 492 md5_state_t md5; 493 md5_byte_t digest[16]; 494 495 rc_api_url_build_dorequest_url(request); 496 497 if (api_params->leaderboard_id == 0) 498 return RC_INVALID_STATE; 499 500 rc_url_builder_init(&builder, &request->buffer, 96); 501 if (rc_api_url_build_dorequest(&builder, "submitlbentry", api_params->username, api_params->api_token)) { 502 rc_url_builder_append_unum_param(&builder, "i", api_params->leaderboard_id); 503 rc_url_builder_append_num_param(&builder, "s", api_params->score); 504 505 if (api_params->game_hash && *api_params->game_hash) 506 rc_url_builder_append_str_param(&builder, "m", api_params->game_hash); 507 508 /* Evaluate the signature. */ 509 md5_init(&md5); 510 snprintf(buffer, sizeof(buffer), "%u", api_params->leaderboard_id); 511 md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer)); 512 md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username)); 513 snprintf(buffer, sizeof(buffer), "%d", api_params->score); 514 md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer)); 515 md5_finish(&md5, digest); 516 rc_format_md5(buffer, digest); 517 rc_url_builder_append_str_param(&builder, "v", buffer); 518 519 request->post_data = rc_url_builder_finalize(&builder); 520 request->content_type = RC_CONTENT_TYPE_URLENCODED; 521 } 522 523 return builder.result; 524 } 525 526 int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response) { 527 rc_api_server_response_t response_obj; 528 529 memset(&response_obj, 0, sizeof(response_obj)); 530 response_obj.body = server_response; 531 response_obj.body_length = rc_json_get_object_string_length(server_response); 532 533 return rc_api_process_submit_lboard_entry_server_response(response, &response_obj); 534 } 535 536 int rc_api_process_submit_lboard_entry_server_response(rc_api_submit_lboard_entry_response_t* response, const rc_api_server_response_t* server_response) { 537 rc_api_lboard_entry_t* entry; 538 rc_json_field_t array_field; 539 rc_json_iterator_t iterator; 540 const char* str; 541 int result; 542 543 rc_json_field_t fields[] = { 544 RC_JSON_NEW_FIELD("Success"), 545 RC_JSON_NEW_FIELD("Error"), 546 RC_JSON_NEW_FIELD("Response") /* nested object */ 547 }; 548 549 rc_json_field_t response_fields[] = { 550 RC_JSON_NEW_FIELD("Score"), 551 RC_JSON_NEW_FIELD("BestScore"), 552 RC_JSON_NEW_FIELD("RankInfo"), /* nested object */ 553 RC_JSON_NEW_FIELD("TopEntries") /* array */ 554 /* unused fields 555 RC_JSON_NEW_FIELD("LBData"), / * array * / 556 RC_JSON_NEW_FIELD("ScoreFormatted"), 557 RC_JSON_NEW_FIELD("TopEntriesFriends") / * array * / 558 * unused fields */ 559 }; 560 561 /* unused fields 562 rc_json_field_t lbdata_fields[] = { 563 RC_JSON_NEW_FIELD("Format"), 564 RC_JSON_NEW_FIELD("LeaderboardID"), 565 RC_JSON_NEW_FIELD("GameID"), 566 RC_JSON_NEW_FIELD("Title"), 567 RC_JSON_NEW_FIELD("LowerIsBetter") 568 }; 569 * unused fields */ 570 571 rc_json_field_t entry_fields[] = { 572 RC_JSON_NEW_FIELD("User"), 573 RC_JSON_NEW_FIELD("Rank"), 574 RC_JSON_NEW_FIELD("Score") 575 /* unused fields 576 RC_JSON_NEW_FIELD("DateSubmitted") 577 * unused fields */ 578 }; 579 580 rc_json_field_t rank_info_fields[] = { 581 RC_JSON_NEW_FIELD("Rank"), 582 RC_JSON_NEW_FIELD("NumEntries") 583 /* unused fields 584 RC_JSON_NEW_FIELD("LowerIsBetter"), 585 RC_JSON_NEW_FIELD("UserRank") 586 * unused fields */ 587 }; 588 589 memset(response, 0, sizeof(*response)); 590 rc_buffer_init(&response->response.buffer); 591 592 result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); 593 if (result != RC_OK || !response->response.succeeded) 594 return result; 595 596 if (!rc_json_get_required_object(response_fields, sizeof(response_fields) / sizeof(response_fields[0]), &response->response, &fields[2], "Response")) 597 return RC_MISSING_VALUE; 598 if (!rc_json_get_required_num(&response->submitted_score, &response->response, &response_fields[0], "Score")) 599 return RC_MISSING_VALUE; 600 if (!rc_json_get_required_num(&response->best_score, &response->response, &response_fields[1], "BestScore")) 601 return RC_MISSING_VALUE; 602 603 if (!rc_json_get_required_object(rank_info_fields, sizeof(rank_info_fields) / sizeof(rank_info_fields[0]), &response->response, &response_fields[2], "RankInfo")) 604 return RC_MISSING_VALUE; 605 if (!rc_json_get_required_unum(&response->new_rank, &response->response, &rank_info_fields[0], "Rank")) 606 return RC_MISSING_VALUE; 607 if (!rc_json_get_required_string(&str, &response->response, &rank_info_fields[1], "NumEntries")) 608 return RC_MISSING_VALUE; 609 response->num_entries = (unsigned)atoi(str); 610 611 if (!rc_json_get_required_array(&response->num_top_entries, &array_field, &response->response, &response_fields[3], "TopEntries")) 612 return RC_MISSING_VALUE; 613 614 if (response->num_top_entries) { 615 response->top_entries = (rc_api_lboard_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_top_entries * sizeof(rc_api_lboard_entry_t)); 616 if (!response->top_entries) 617 return RC_OUT_OF_MEMORY; 618 619 memset(&iterator, 0, sizeof(iterator)); 620 iterator.json = array_field.value_start; 621 iterator.end = array_field.value_end; 622 623 entry = response->top_entries; 624 while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) { 625 if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User")) 626 return RC_MISSING_VALUE; 627 628 if (!rc_json_get_required_unum(&entry->rank, &response->response, &entry_fields[1], "Rank")) 629 return RC_MISSING_VALUE; 630 631 if (!rc_json_get_required_num(&entry->score, &response->response, &entry_fields[2], "Score")) 632 return RC_MISSING_VALUE; 633 634 ++entry; 635 } 636 } 637 638 return RC_OK; 639 } 640 641 void rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response) { 642 rc_buffer_destroy(&response->response.buffer); 643 }