rc_api_editor.c (20212B)
1 #include "rc_api_editor.h" 2 #include "rc_api_common.h" 3 #include "rc_api_runtime.h" 4 5 #include "../rc_compat.h" 6 #include "../rhash/md5.h" 7 8 #include <stdlib.h> 9 #include <string.h> 10 11 /* --- Fetch Code Notes --- */ 12 13 int rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params) { 14 rc_api_url_builder_t builder; 15 16 rc_api_url_build_dorequest_url(request); 17 18 if (api_params->game_id == 0) 19 return RC_INVALID_STATE; 20 21 rc_url_builder_init(&builder, &request->buffer, 48); 22 rc_url_builder_append_str_param(&builder, "r", "codenotes2"); 23 rc_url_builder_append_unum_param(&builder, "g", api_params->game_id); 24 25 request->post_data = rc_url_builder_finalize(&builder); 26 request->content_type = RC_CONTENT_TYPE_URLENCODED; 27 28 return builder.result; 29 } 30 31 int rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response, const char* server_response) { 32 rc_api_server_response_t response_obj; 33 34 memset(&response_obj, 0, sizeof(response_obj)); 35 response_obj.body = server_response; 36 response_obj.body_length = rc_json_get_object_string_length(server_response); 37 38 return rc_api_process_fetch_code_notes_server_response(response, &response_obj); 39 } 40 41 int rc_api_process_fetch_code_notes_server_response(rc_api_fetch_code_notes_response_t* response, const rc_api_server_response_t* server_response) { 42 rc_json_field_t array_field; 43 rc_json_iterator_t iterator; 44 rc_api_code_note_t* note; 45 const char* address_str; 46 const char* last_author = ""; 47 size_t last_author_len = 0; 48 size_t len; 49 int result; 50 51 rc_json_field_t fields[] = { 52 RC_JSON_NEW_FIELD("Success"), 53 RC_JSON_NEW_FIELD("Error"), 54 RC_JSON_NEW_FIELD("CodeNotes") 55 }; 56 57 rc_json_field_t note_fields[] = { 58 RC_JSON_NEW_FIELD("Address"), 59 RC_JSON_NEW_FIELD("User"), 60 RC_JSON_NEW_FIELD("Note") 61 }; 62 63 memset(response, 0, sizeof(*response)); 64 rc_buffer_init(&response->response.buffer); 65 66 result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); 67 if (result != RC_OK || !response->response.succeeded) 68 return result; 69 70 if (!rc_json_get_required_array(&response->num_notes, &array_field, &response->response, &fields[2], "CodeNotes")) 71 return RC_MISSING_VALUE; 72 73 if (response->num_notes) { 74 response->notes = (rc_api_code_note_t*)rc_buffer_alloc(&response->response.buffer, response->num_notes * sizeof(rc_api_code_note_t)); 75 if (!response->notes) 76 return RC_OUT_OF_MEMORY; 77 78 memset(&iterator, 0, sizeof(iterator)); 79 iterator.json = array_field.value_start; 80 iterator.end = array_field.value_end; 81 82 note = response->notes; 83 while (rc_json_get_array_entry_object(note_fields, sizeof(note_fields) / sizeof(note_fields[0]), &iterator)) { 84 /* an empty note represents a record that was deleted on the server */ 85 /* a note set to '' also represents a deleted note (remnant of a bug) */ 86 /* NOTE: len will include the quotes */ 87 if (note_fields[2].value_start) { 88 len = note_fields[2].value_end - note_fields[2].value_start; 89 if (len == 2 || (len == 4 && note_fields[2].value_start[1] == '\'' && note_fields[2].value_start[2] == '\'')) { 90 --response->num_notes; 91 continue; 92 } 93 } 94 95 if (!rc_json_get_required_string(&address_str, &response->response, ¬e_fields[0], "Address")) 96 return RC_MISSING_VALUE; 97 note->address = (unsigned)strtol(address_str, NULL, 16); 98 if (!rc_json_get_required_string(¬e->note, &response->response, ¬e_fields[2], "Note")) 99 return RC_MISSING_VALUE; 100 101 len = note_fields[1].value_end - note_fields[1].value_start; 102 if (len == last_author_len && memcmp(note_fields[1].value_start, last_author, len) == 0) { 103 note->author = last_author; 104 } 105 else { 106 if (!rc_json_get_required_string(¬e->author, &response->response, ¬e_fields[1], "User")) 107 return RC_MISSING_VALUE; 108 109 last_author = note->author; 110 last_author_len = len; 111 } 112 113 ++note; 114 } 115 } 116 117 return RC_OK; 118 } 119 120 void rc_api_destroy_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response) { 121 rc_buffer_destroy(&response->response.buffer); 122 } 123 124 /* --- Update Code Note --- */ 125 126 int rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params) { 127 rc_api_url_builder_t builder; 128 129 rc_api_url_build_dorequest_url(request); 130 131 if (api_params->game_id == 0) 132 return RC_INVALID_STATE; 133 134 rc_url_builder_init(&builder, &request->buffer, 128); 135 if (!rc_api_url_build_dorequest(&builder, "submitcodenote", api_params->username, api_params->api_token)) 136 return builder.result; 137 138 rc_url_builder_append_unum_param(&builder, "g", api_params->game_id); 139 rc_url_builder_append_unum_param(&builder, "m", api_params->address); 140 141 if (api_params->note && *api_params->note) 142 rc_url_builder_append_str_param(&builder, "n", api_params->note); 143 144 request->post_data = rc_url_builder_finalize(&builder); 145 request->content_type = RC_CONTENT_TYPE_URLENCODED; 146 147 return builder.result; 148 } 149 150 int rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response) { 151 rc_api_server_response_t response_obj; 152 153 memset(&response_obj, 0, sizeof(response_obj)); 154 response_obj.body = server_response; 155 response_obj.body_length = rc_json_get_object_string_length(server_response); 156 157 return rc_api_process_update_code_note_server_response(response, &response_obj); 158 } 159 160 int rc_api_process_update_code_note_server_response(rc_api_update_code_note_response_t* response, const rc_api_server_response_t* server_response) { 161 int result; 162 163 rc_json_field_t fields[] = { 164 RC_JSON_NEW_FIELD("Success"), 165 RC_JSON_NEW_FIELD("Error") 166 /* unused fields 167 RC_JSON_NEW_FIELD("GameID"), 168 RC_JSON_NEW_FIELD("Address"), 169 RC_JSON_NEW_FIELD("Note") 170 */ 171 }; 172 173 memset(response, 0, sizeof(*response)); 174 rc_buffer_init(&response->response.buffer); 175 176 result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); 177 if (result != RC_OK || !response->response.succeeded) 178 return result; 179 180 return RC_OK; 181 } 182 183 void rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t* response) { 184 rc_buffer_destroy(&response->response.buffer); 185 } 186 187 /* --- Update Achievement --- */ 188 189 static const char* rc_type_string(uint32_t type) { 190 switch (type) { 191 case RC_ACHIEVEMENT_TYPE_MISSABLE: return "missable"; 192 case RC_ACHIEVEMENT_TYPE_PROGRESSION: return "progression"; 193 case RC_ACHIEVEMENT_TYPE_WIN: return "win_condition"; 194 default: return ""; 195 } 196 } 197 198 int rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params) { 199 rc_api_url_builder_t builder; 200 char buffer[33]; 201 md5_state_t md5; 202 md5_byte_t hash[16]; 203 204 rc_api_url_build_dorequest_url(request); 205 206 if (api_params->game_id == 0 || api_params->category == 0) 207 return RC_INVALID_STATE; 208 if (!api_params->title || !*api_params->title) 209 return RC_INVALID_STATE; 210 if (!api_params->description || !*api_params->description) 211 return RC_INVALID_STATE; 212 if (!api_params->trigger || !*api_params->trigger) 213 return RC_INVALID_STATE; 214 215 rc_url_builder_init(&builder, &request->buffer, 128); 216 if (!rc_api_url_build_dorequest(&builder, "uploadachievement", api_params->username, api_params->api_token)) 217 return builder.result; 218 219 if (api_params->achievement_id) 220 rc_url_builder_append_unum_param(&builder, "a", api_params->achievement_id); 221 rc_url_builder_append_unum_param(&builder, "g", api_params->game_id); 222 rc_url_builder_append_str_param(&builder, "n", api_params->title); 223 rc_url_builder_append_str_param(&builder, "d", api_params->description); 224 rc_url_builder_append_str_param(&builder, "m", api_params->trigger); 225 rc_url_builder_append_unum_param(&builder, "z", api_params->points); 226 rc_url_builder_append_unum_param(&builder, "f", api_params->category); 227 if (api_params->badge) 228 rc_url_builder_append_str_param(&builder, "b", api_params->badge); 229 rc_url_builder_append_str_param(&builder, "x", rc_type_string(api_params->type)); 230 231 /* Evaluate the signature. */ 232 md5_init(&md5); 233 md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username)); 234 md5_append(&md5, (md5_byte_t*)"SECRET", 6); 235 snprintf(buffer, sizeof(buffer), "%u", api_params->achievement_id); 236 md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer)); 237 md5_append(&md5, (md5_byte_t*)"SEC", 3); 238 md5_append(&md5, (md5_byte_t*)api_params->trigger, (int)strlen(api_params->trigger)); 239 snprintf(buffer, sizeof(buffer), "%u", api_params->points); 240 md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer)); 241 md5_append(&md5, (md5_byte_t*)"RE2", 3); 242 snprintf(buffer, sizeof(buffer), "%u", api_params->points * 3); 243 md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer)); 244 md5_finish(&md5, hash); 245 rc_format_md5(buffer, hash); 246 rc_url_builder_append_str_param(&builder, "h", buffer); 247 248 request->post_data = rc_url_builder_finalize(&builder); 249 request->content_type = RC_CONTENT_TYPE_URLENCODED; 250 251 return builder.result; 252 } 253 254 int rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response) { 255 rc_api_server_response_t response_obj; 256 257 memset(&response_obj, 0, sizeof(response_obj)); 258 response_obj.body = server_response; 259 response_obj.body_length = rc_json_get_object_string_length(server_response); 260 261 return rc_api_process_update_achievement_server_response(response, &response_obj); 262 } 263 264 int rc_api_process_update_achievement_server_response(rc_api_update_achievement_response_t* response, const rc_api_server_response_t* server_response) { 265 int result; 266 267 rc_json_field_t fields[] = { 268 RC_JSON_NEW_FIELD("Success"), 269 RC_JSON_NEW_FIELD("Error"), 270 RC_JSON_NEW_FIELD("AchievementID") 271 }; 272 273 memset(response, 0, sizeof(*response)); 274 rc_buffer_init(&response->response.buffer); 275 276 result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); 277 if (result != RC_OK || !response->response.succeeded) 278 return result; 279 280 if (!rc_json_get_required_unum(&response->achievement_id, &response->response, &fields[2], "AchievementID")) 281 return RC_MISSING_VALUE; 282 283 return RC_OK; 284 } 285 286 void rc_api_destroy_update_achievement_response(rc_api_update_achievement_response_t* response) { 287 rc_buffer_destroy(&response->response.buffer); 288 } 289 290 /* --- Update Leaderboard --- */ 291 292 int rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params) { 293 rc_api_url_builder_t builder; 294 char buffer[33]; 295 md5_state_t md5; 296 md5_byte_t hash[16]; 297 298 rc_api_url_build_dorequest_url(request); 299 300 if (api_params->game_id == 0) 301 return RC_INVALID_STATE; 302 if (!api_params->title || !*api_params->title) 303 return RC_INVALID_STATE; 304 if (!api_params->description) 305 return RC_INVALID_STATE; 306 if (!api_params->start_trigger || !*api_params->start_trigger) 307 return RC_INVALID_STATE; 308 if (!api_params->submit_trigger || !*api_params->submit_trigger) 309 return RC_INVALID_STATE; 310 if (!api_params->cancel_trigger || !*api_params->cancel_trigger) 311 return RC_INVALID_STATE; 312 if (!api_params->value_definition || !*api_params->value_definition) 313 return RC_INVALID_STATE; 314 if (!api_params->format || !*api_params->format) 315 return RC_INVALID_STATE; 316 317 rc_url_builder_init(&builder, &request->buffer, 128); 318 if (!rc_api_url_build_dorequest(&builder, "uploadleaderboard", api_params->username, api_params->api_token)) 319 return builder.result; 320 321 if (api_params->leaderboard_id) 322 rc_url_builder_append_unum_param(&builder, "i", api_params->leaderboard_id); 323 rc_url_builder_append_unum_param(&builder, "g", api_params->game_id); 324 rc_url_builder_append_str_param(&builder, "n", api_params->title); 325 rc_url_builder_append_str_param(&builder, "d", api_params->description); 326 rc_url_builder_append_str_param(&builder, "s", api_params->start_trigger); 327 rc_url_builder_append_str_param(&builder, "b", api_params->submit_trigger); 328 rc_url_builder_append_str_param(&builder, "c", api_params->cancel_trigger); 329 rc_url_builder_append_str_param(&builder, "l", api_params->value_definition); 330 rc_url_builder_append_num_param(&builder, "w", api_params->lower_is_better); 331 rc_url_builder_append_str_param(&builder, "f", api_params->format); 332 333 /* Evaluate the signature. */ 334 md5_init(&md5); 335 md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username)); 336 md5_append(&md5, (md5_byte_t*)"SECRET", 6); 337 snprintf(buffer, sizeof(buffer), "%u", api_params->leaderboard_id); 338 md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer)); 339 md5_append(&md5, (md5_byte_t*)"SEC", 3); 340 md5_append(&md5, (md5_byte_t*)api_params->start_trigger, (int)strlen(api_params->start_trigger)); 341 md5_append(&md5, (md5_byte_t*)api_params->submit_trigger, (int)strlen(api_params->submit_trigger)); 342 md5_append(&md5, (md5_byte_t*)api_params->cancel_trigger, (int)strlen(api_params->cancel_trigger)); 343 md5_append(&md5, (md5_byte_t*)api_params->value_definition, (int)strlen(api_params->value_definition)); 344 md5_append(&md5, (md5_byte_t*)"RE2", 3); 345 md5_append(&md5, (md5_byte_t*)api_params->format, (int)strlen(api_params->format)); 346 md5_finish(&md5, hash); 347 rc_format_md5(buffer, hash); 348 rc_url_builder_append_str_param(&builder, "h", buffer); 349 350 request->post_data = rc_url_builder_finalize(&builder); 351 request->content_type = RC_CONTENT_TYPE_URLENCODED; 352 353 return builder.result; 354 } 355 356 int rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response) { 357 rc_api_server_response_t response_obj; 358 359 memset(&response_obj, 0, sizeof(response_obj)); 360 response_obj.body = server_response; 361 response_obj.body_length = rc_json_get_object_string_length(server_response); 362 363 return rc_api_process_update_leaderboard_server_response(response, &response_obj); 364 } 365 366 int rc_api_process_update_leaderboard_server_response(rc_api_update_leaderboard_response_t* response, const rc_api_server_response_t* server_response) { 367 int result; 368 369 rc_json_field_t fields[] = { 370 RC_JSON_NEW_FIELD("Success"), 371 RC_JSON_NEW_FIELD("Error"), 372 RC_JSON_NEW_FIELD("LeaderboardID") 373 }; 374 375 memset(response, 0, sizeof(*response)); 376 rc_buffer_init(&response->response.buffer); 377 378 result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); 379 if (result != RC_OK || !response->response.succeeded) 380 return result; 381 382 if (!rc_json_get_required_unum(&response->leaderboard_id, &response->response, &fields[2], "LeaderboardID")) 383 return RC_MISSING_VALUE; 384 385 return RC_OK; 386 } 387 388 void rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_response_t* response) { 389 rc_buffer_destroy(&response->response.buffer); 390 } 391 392 /* --- Fetch Badge Range --- */ 393 394 int rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params) { 395 rc_api_url_builder_t builder; 396 397 rc_api_url_build_dorequest_url(request); 398 399 rc_url_builder_init(&builder, &request->buffer, 48); 400 rc_url_builder_append_str_param(&builder, "r", "badgeiter"); 401 402 request->post_data = rc_url_builder_finalize(&builder); 403 request->content_type = RC_CONTENT_TYPE_URLENCODED; 404 405 (void)api_params; 406 407 return builder.result; 408 } 409 410 int rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response) { 411 rc_api_server_response_t response_obj; 412 413 memset(&response_obj, 0, sizeof(response_obj)); 414 response_obj.body = server_response; 415 response_obj.body_length = rc_json_get_object_string_length(server_response); 416 417 return rc_api_process_fetch_badge_range_server_response(response, &response_obj); 418 } 419 420 int rc_api_process_fetch_badge_range_server_response(rc_api_fetch_badge_range_response_t* response, const rc_api_server_response_t* server_response) { 421 int result; 422 423 rc_json_field_t fields[] = { 424 RC_JSON_NEW_FIELD("Success"), 425 RC_JSON_NEW_FIELD("Error"), 426 RC_JSON_NEW_FIELD("FirstBadge"), 427 RC_JSON_NEW_FIELD("NextBadge") 428 }; 429 430 memset(response, 0, sizeof(*response)); 431 rc_buffer_init(&response->response.buffer); 432 433 result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); 434 if (result != RC_OK || !response->response.succeeded) 435 return result; 436 437 if (!rc_json_get_required_unum(&response->first_badge_id, &response->response, &fields[2], "FirstBadge")) 438 return RC_MISSING_VALUE; 439 if (!rc_json_get_required_unum(&response->next_badge_id, &response->response, &fields[3], "NextBadge")) 440 return RC_MISSING_VALUE; 441 442 return RC_OK; 443 } 444 445 void rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response) { 446 rc_buffer_destroy(&response->response.buffer); 447 } 448 449 /* --- Add Game Hash --- */ 450 451 int rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params) { 452 rc_api_url_builder_t builder; 453 454 rc_api_url_build_dorequest_url(request); 455 456 if (api_params->console_id == 0) 457 return RC_INVALID_STATE; 458 if (!api_params->hash || !*api_params->hash) 459 return RC_INVALID_STATE; 460 if (api_params->game_id == 0 && (!api_params->title || !*api_params->title)) 461 return RC_INVALID_STATE; 462 463 rc_url_builder_init(&builder, &request->buffer, 128); 464 if (!rc_api_url_build_dorequest(&builder, "submitgametitle", api_params->username, api_params->api_token)) 465 return builder.result; 466 467 rc_url_builder_append_unum_param(&builder, "c", api_params->console_id); 468 rc_url_builder_append_str_param(&builder, "m", api_params->hash); 469 if (api_params->title) 470 rc_url_builder_append_str_param(&builder, "i", api_params->title); 471 if (api_params->game_id) 472 rc_url_builder_append_unum_param(&builder, "g", api_params->game_id); 473 if (api_params->hash_description && *api_params->hash_description) 474 rc_url_builder_append_str_param(&builder, "d", api_params->hash_description); 475 476 request->post_data = rc_url_builder_finalize(&builder); 477 request->content_type = RC_CONTENT_TYPE_URLENCODED; 478 479 return builder.result; 480 } 481 482 int rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response) { 483 rc_api_server_response_t response_obj; 484 485 memset(&response_obj, 0, sizeof(response_obj)); 486 response_obj.body = server_response; 487 response_obj.body_length = rc_json_get_object_string_length(server_response); 488 489 return rc_api_process_add_game_hash_server_response(response, &response_obj); 490 } 491 492 int rc_api_process_add_game_hash_server_response(rc_api_add_game_hash_response_t* response, const rc_api_server_response_t* server_response) { 493 int result; 494 495 rc_json_field_t fields[] = { 496 RC_JSON_NEW_FIELD("Success"), 497 RC_JSON_NEW_FIELD("Error"), 498 RC_JSON_NEW_FIELD("Response") 499 }; 500 501 rc_json_field_t response_fields[] = { 502 RC_JSON_NEW_FIELD("GameID") 503 /* unused fields 504 RC_JSON_NEW_FIELD("MD5"), 505 RC_JSON_NEW_FIELD("ConsoleID"), 506 RC_JSON_NEW_FIELD("GameTitle"), 507 RC_JSON_NEW_FIELD("Success") 508 */ 509 }; 510 511 memset(response, 0, sizeof(*response)); 512 rc_buffer_init(&response->response.buffer); 513 514 result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); 515 if (result != RC_OK || !response->response.succeeded) 516 return result; 517 518 if (!rc_json_get_required_object(response_fields, sizeof(response_fields) / sizeof(response_fields[0]), &response->response, &fields[2], "Response")) 519 return RC_MISSING_VALUE; 520 521 if (!rc_json_get_required_unum(&response->game_id, &response->response, &response_fields[0], "GameID")) 522 return RC_MISSING_VALUE; 523 524 return RC_OK; 525 } 526 527 void rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response) { 528 rc_buffer_destroy(&response->response.buffer); 529 }