duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

rc_api_info.c (17573B)


      1 #include "rc_api_info.h"
      2 #include "rc_api_common.h"
      3 
      4 #include "rc_runtime_types.h"
      5 
      6 #include "../rc_compat.h"
      7 
      8 #include <stdlib.h>
      9 #include <string.h>
     10 
     11 /* --- Fetch Achievement Info --- */
     12 
     13 int rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_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->achievement_id == 0)
     19     return RC_INVALID_STATE;
     20 
     21   rc_url_builder_init(&builder, &request->buffer, 48);
     22   if (rc_api_url_build_dorequest(&builder, "achievementwondata", api_params->username, api_params->api_token)) {
     23     rc_url_builder_append_unum_param(&builder, "a", api_params->achievement_id);
     24 
     25     if (api_params->friends_only)
     26       rc_url_builder_append_unum_param(&builder, "f", 1);
     27     if (api_params->first_entry > 1)
     28       rc_url_builder_append_unum_param(&builder, "o", api_params->first_entry - 1); /* number of entries to skip */
     29     rc_url_builder_append_unum_param(&builder, "c", api_params->count);
     30 
     31     request->post_data = rc_url_builder_finalize(&builder);
     32     request->content_type = RC_CONTENT_TYPE_URLENCODED;
     33   }
     34 
     35   return builder.result;
     36 }
     37 
     38 int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response) {
     39   rc_api_server_response_t response_obj;
     40 
     41   memset(&response_obj, 0, sizeof(response_obj));
     42   response_obj.body = server_response;
     43   response_obj.body_length = rc_json_get_object_string_length(server_response);
     44 
     45   return rc_api_process_fetch_achievement_info_server_response(response, &response_obj);
     46 }
     47 
     48 int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achievement_info_response_t* response, const rc_api_server_response_t* server_response) {
     49   rc_api_achievement_awarded_entry_t* entry;
     50   rc_json_field_t array_field;
     51   rc_json_iterator_t iterator;
     52   uint32_t timet;
     53   int result;
     54 
     55   rc_json_field_t fields[] = {
     56     RC_JSON_NEW_FIELD("Success"),
     57     RC_JSON_NEW_FIELD("Error"),
     58     RC_JSON_NEW_FIELD("AchievementID"),
     59     RC_JSON_NEW_FIELD("Response")
     60     /* unused fields
     61     RC_JSON_NEW_FIELD("Offset"),
     62     RC_JSON_NEW_FIELD("Count"),
     63     RC_JSON_NEW_FIELD("FriendsOnly")
     64      * unused fields */
     65   };
     66 
     67   rc_json_field_t response_fields[] = {
     68     RC_JSON_NEW_FIELD("NumEarned"),
     69     RC_JSON_NEW_FIELD("TotalPlayers"),
     70     RC_JSON_NEW_FIELD("GameID"),
     71     RC_JSON_NEW_FIELD("RecentWinner") /* array */
     72   };
     73 
     74   rc_json_field_t entry_fields[] = {
     75     RC_JSON_NEW_FIELD("User"),
     76     RC_JSON_NEW_FIELD("DateAwarded")
     77   };
     78 
     79   memset(response, 0, sizeof(*response));
     80   rc_buffer_init(&response->response.buffer);
     81 
     82   result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
     83   if (result != RC_OK)
     84     return result;
     85 
     86   if (!rc_json_get_required_unum(&response->id, &response->response, &fields[2], "AchievementID"))
     87       return RC_MISSING_VALUE;
     88   if (!rc_json_get_required_object(response_fields, sizeof(response_fields) / sizeof(response_fields[0]), &response->response, &fields[3], "Response"))
     89     return RC_MISSING_VALUE;
     90 
     91   if (!rc_json_get_required_unum(&response->num_awarded, &response->response, &response_fields[0], "NumEarned"))
     92     return RC_MISSING_VALUE;
     93   if (!rc_json_get_required_unum(&response->num_players, &response->response, &response_fields[1], "TotalPlayers"))
     94     return RC_MISSING_VALUE;
     95   if (!rc_json_get_required_unum(&response->game_id, &response->response, &response_fields[2], "GameID"))
     96     return RC_MISSING_VALUE;
     97 
     98   if (!rc_json_get_required_array(&response->num_recently_awarded, &array_field, &response->response, &response_fields[3], "RecentWinner"))
     99     return RC_MISSING_VALUE;
    100 
    101   if (response->num_recently_awarded) {
    102     response->recently_awarded = (rc_api_achievement_awarded_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_recently_awarded * sizeof(rc_api_achievement_awarded_entry_t));
    103     if (!response->recently_awarded)
    104       return RC_OUT_OF_MEMORY;
    105 
    106     memset(&iterator, 0, sizeof(iterator));
    107     iterator.json = array_field.value_start;
    108     iterator.end = array_field.value_end;
    109 
    110     entry = response->recently_awarded;
    111     while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {
    112       if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User"))
    113         return RC_MISSING_VALUE;
    114 
    115       if (!rc_json_get_required_unum(&timet, &response->response, &entry_fields[1], "DateAwarded"))
    116         return RC_MISSING_VALUE;
    117       entry->awarded = (time_t)timet;
    118 
    119       ++entry;
    120     }
    121   }
    122 
    123   return RC_OK;
    124 }
    125 
    126 void rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response) {
    127   rc_buffer_destroy(&response->response.buffer);
    128 }
    129 
    130 /* --- Fetch Leaderboard Info --- */
    131 
    132 int rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params) {
    133   rc_api_url_builder_t builder;
    134 
    135   rc_api_url_build_dorequest_url(request);
    136 
    137   if (api_params->leaderboard_id == 0)
    138     return RC_INVALID_STATE;
    139 
    140   rc_url_builder_init(&builder, &request->buffer, 48);
    141   rc_url_builder_append_str_param(&builder, "r", "lbinfo");
    142   rc_url_builder_append_unum_param(&builder, "i", api_params->leaderboard_id);
    143 
    144   if (api_params->username)
    145     rc_url_builder_append_str_param(&builder, "u", api_params->username);
    146   else if (api_params->first_entry > 1)
    147     rc_url_builder_append_unum_param(&builder, "o", api_params->first_entry - 1); /* number of entries to skip */
    148 
    149   rc_url_builder_append_unum_param(&builder, "c", api_params->count);
    150   request->post_data = rc_url_builder_finalize(&builder);
    151   request->content_type = RC_CONTENT_TYPE_URLENCODED;
    152 
    153   return builder.result;
    154 }
    155 
    156 int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response) {
    157   rc_api_server_response_t response_obj;
    158 
    159   memset(&response_obj, 0, sizeof(response_obj));
    160   response_obj.body = server_response;
    161   response_obj.body_length = rc_json_get_object_string_length(server_response);
    162 
    163   return rc_api_process_fetch_leaderboard_info_server_response(response, &response_obj);
    164 }
    165 
    166 int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboard_info_response_t* response, const rc_api_server_response_t* server_response) {
    167   rc_api_lboard_info_entry_t* entry;
    168   rc_json_field_t array_field;
    169   rc_json_iterator_t iterator;
    170   uint32_t timet;
    171   int result;
    172   size_t len;
    173   char format[16];
    174 
    175   rc_json_field_t fields[] = {
    176     RC_JSON_NEW_FIELD("Success"),
    177     RC_JSON_NEW_FIELD("Error"),
    178     RC_JSON_NEW_FIELD("LeaderboardData")
    179   };
    180 
    181   rc_json_field_t leaderboarddata_fields[] = {
    182     RC_JSON_NEW_FIELD("LBID"),
    183     RC_JSON_NEW_FIELD("LBFormat"),
    184     RC_JSON_NEW_FIELD("LowerIsBetter"),
    185     RC_JSON_NEW_FIELD("LBTitle"),
    186     RC_JSON_NEW_FIELD("LBDesc"),
    187     RC_JSON_NEW_FIELD("LBMem"),
    188     RC_JSON_NEW_FIELD("GameID"),
    189     RC_JSON_NEW_FIELD("LBAuthor"),
    190     RC_JSON_NEW_FIELD("LBCreated"),
    191     RC_JSON_NEW_FIELD("LBUpdated"),
    192     RC_JSON_NEW_FIELD("Entries"), /* array */
    193     RC_JSON_NEW_FIELD("TotalEntries")
    194     /* unused fields
    195     RC_JSON_NEW_FIELD("GameTitle"),
    196     RC_JSON_NEW_FIELD("ConsoleID"),
    197     RC_JSON_NEW_FIELD("ConsoleName"),
    198     RC_JSON_NEW_FIELD("ForumTopicID"),
    199     RC_JSON_NEW_FIELD("GameIcon")
    200      * unused fields */
    201   };
    202 
    203   rc_json_field_t entry_fields[] = {
    204     RC_JSON_NEW_FIELD("User"),
    205     RC_JSON_NEW_FIELD("Rank"),
    206     RC_JSON_NEW_FIELD("Index"),
    207     RC_JSON_NEW_FIELD("Score"),
    208     RC_JSON_NEW_FIELD("DateSubmitted")
    209   };
    210 
    211   memset(response, 0, sizeof(*response));
    212   rc_buffer_init(&response->response.buffer);
    213 
    214   result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
    215   if (result != RC_OK)
    216     return result;
    217 
    218   if (!rc_json_get_required_object(leaderboarddata_fields, sizeof(leaderboarddata_fields) / sizeof(leaderboarddata_fields[0]), &response->response, &fields[2], "LeaderboardData"))
    219     return RC_MISSING_VALUE;
    220 
    221   if (!rc_json_get_required_unum(&response->id, &response->response, &leaderboarddata_fields[0], "LBID"))
    222     return RC_MISSING_VALUE;
    223   if (!rc_json_get_required_unum(&response->lower_is_better, &response->response, &leaderboarddata_fields[2], "LowerIsBetter"))
    224     return RC_MISSING_VALUE;
    225   if (!rc_json_get_required_string(&response->title, &response->response, &leaderboarddata_fields[3], "LBTitle"))
    226     return RC_MISSING_VALUE;
    227   if (!rc_json_get_required_string(&response->description, &response->response, &leaderboarddata_fields[4], "LBDesc"))
    228     return RC_MISSING_VALUE;
    229   if (!rc_json_get_required_string(&response->definition, &response->response, &leaderboarddata_fields[5], "LBMem"))
    230     return RC_MISSING_VALUE;
    231   if (!rc_json_get_required_unum(&response->game_id, &response->response, &leaderboarddata_fields[6], "GameID"))
    232     return RC_MISSING_VALUE;
    233   if (!rc_json_get_required_string(&response->author, &response->response, &leaderboarddata_fields[7], "LBAuthor"))
    234     return RC_MISSING_VALUE;
    235   if (!rc_json_get_required_datetime(&response->created, &response->response, &leaderboarddata_fields[8], "LBCreated"))
    236     return RC_MISSING_VALUE;
    237   if (!rc_json_get_required_datetime(&response->updated, &response->response, &leaderboarddata_fields[9], "LBUpdated"))
    238     return RC_MISSING_VALUE;
    239   if (!rc_json_get_required_unum(&response->total_entries, &response->response, &leaderboarddata_fields[11], "TotalEntries"))
    240     return RC_MISSING_VALUE;
    241 
    242   if (!leaderboarddata_fields[1].value_end)
    243     return RC_MISSING_VALUE;
    244   len = leaderboarddata_fields[1].value_end - leaderboarddata_fields[1].value_start - 2;
    245   if (len < sizeof(format) - 1) {
    246     memcpy(format, leaderboarddata_fields[1].value_start + 1, len);
    247     format[len] = '\0';
    248     response->format = rc_parse_format(format);
    249   }
    250   else {
    251     response->format = RC_FORMAT_VALUE;
    252   }
    253 
    254   if (!rc_json_get_required_array(&response->num_entries, &array_field, &response->response, &leaderboarddata_fields[10], "Entries"))
    255     return RC_MISSING_VALUE;
    256 
    257   if (response->num_entries) {
    258     response->entries = (rc_api_lboard_info_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_lboard_info_entry_t));
    259     if (!response->entries)
    260       return RC_OUT_OF_MEMORY;
    261 
    262     memset(&iterator, 0, sizeof(iterator));
    263     iterator.json = array_field.value_start;
    264     iterator.end = array_field.value_end;
    265 
    266     entry = response->entries;
    267     while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {
    268       if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User"))
    269         return RC_MISSING_VALUE;
    270 
    271       if (!rc_json_get_required_unum(&entry->rank, &response->response, &entry_fields[1], "Rank"))
    272         return RC_MISSING_VALUE;
    273 
    274       if (!rc_json_get_required_unum(&entry->index, &response->response, &entry_fields[2], "Index"))
    275         return RC_MISSING_VALUE;
    276 
    277       if (!rc_json_get_required_num(&entry->score, &response->response, &entry_fields[3], "Score"))
    278         return RC_MISSING_VALUE;
    279 
    280       if (!rc_json_get_required_unum(&timet, &response->response, &entry_fields[4], "DateSubmitted"))
    281         return RC_MISSING_VALUE;
    282       entry->submitted = (time_t)timet;
    283 
    284       ++entry;
    285     }
    286   }
    287 
    288   return RC_OK;
    289 }
    290 
    291 void rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response) {
    292   rc_buffer_destroy(&response->response.buffer);
    293 }
    294 
    295 /* --- Fetch Games List --- */
    296 
    297 int rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params) {
    298   rc_api_url_builder_t builder;
    299 
    300   rc_api_url_build_dorequest_url(request);
    301 
    302   if (api_params->console_id == 0)
    303     return RC_INVALID_STATE;
    304 
    305   rc_url_builder_init(&builder, &request->buffer, 48);
    306   rc_url_builder_append_str_param(&builder, "r", "gameslist");
    307   rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);
    308 
    309   request->post_data = rc_url_builder_finalize(&builder);
    310   request->content_type = RC_CONTENT_TYPE_URLENCODED;
    311 
    312   return builder.result;
    313 }
    314 
    315 int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response) {
    316   rc_api_server_response_t response_obj;
    317 
    318   memset(&response_obj, 0, sizeof(response_obj));
    319   response_obj.body = server_response;
    320   response_obj.body_length = rc_json_get_object_string_length(server_response);
    321 
    322   return rc_api_process_fetch_games_list_server_response(response, &response_obj);
    323 }
    324 
    325 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) {
    326   rc_api_game_list_entry_t* entry;
    327   rc_json_iterator_t iterator;
    328   rc_json_field_t field;
    329   int result;
    330   char* end;
    331 
    332   rc_json_field_t fields[] = {
    333     RC_JSON_NEW_FIELD("Success"),
    334     RC_JSON_NEW_FIELD("Error"),
    335     RC_JSON_NEW_FIELD("Response")
    336   };
    337 
    338   memset(response, 0, sizeof(*response));
    339   rc_buffer_init(&response->response.buffer);
    340 
    341   result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
    342   if (result != RC_OK)
    343     return result;
    344 
    345   if (!fields[2].value_start) {
    346     /* call rc_json_get_required_object to generate the error message */
    347     rc_json_get_required_object(NULL, 0, &response->response, &fields[2], "Response");
    348     return RC_MISSING_VALUE;
    349   }
    350 
    351   response->num_entries = fields[2].array_size;
    352   rc_buffer_reserve(&response->response.buffer, response->num_entries * (32 + sizeof(rc_api_game_list_entry_t)));
    353 
    354   response->entries = (rc_api_game_list_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_game_list_entry_t));
    355   if (!response->entries)
    356     return RC_OUT_OF_MEMORY;
    357 
    358   memset(&iterator, 0, sizeof(iterator));
    359   iterator.json = fields[2].value_start;
    360   iterator.end = fields[2].value_end;
    361 
    362   entry = response->entries;
    363   while (rc_json_get_next_object_field(&iterator, &field)) {
    364     entry->id = strtol(field.name, &end, 10);
    365 
    366     field.name = "";
    367     if (!rc_json_get_string(&entry->name, &response->response.buffer, &field, ""))
    368       return RC_MISSING_VALUE;
    369 
    370     ++entry;
    371   }
    372 
    373   return RC_OK;
    374 }
    375 
    376 void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response) {
    377   rc_buffer_destroy(&response->response.buffer);
    378 }
    379 
    380 /* --- Fetch Game Titles --- */
    381 
    382 int rc_api_init_fetch_game_titles_request(rc_api_request_t* request, const rc_api_fetch_game_titles_request_t* api_params) {
    383   rc_api_url_builder_t builder;
    384   char num[16];
    385   uint32_t i;
    386 
    387   rc_api_url_build_dorequest_url(request);
    388 
    389   if (api_params->num_game_ids == 0)
    390     return RC_INVALID_STATE;
    391 
    392   rc_url_builder_init(&builder, &request->buffer, 48);
    393   rc_url_builder_append_str_param(&builder, "r", "gameinfolist");
    394   rc_url_builder_append_unum_param(&builder, "g", api_params->game_ids[0]);
    395 
    396   for (i = 1; i < api_params->num_game_ids; i++) {
    397     int chars = snprintf(num, sizeof(num), "%u", api_params->game_ids[i]);
    398     rc_url_builder_append(&builder, ",", 1);
    399     rc_url_builder_append(&builder, num, chars);
    400   }
    401 
    402   request->post_data = rc_url_builder_finalize(&builder);
    403   request->content_type = RC_CONTENT_TYPE_URLENCODED;
    404 
    405   return builder.result;
    406 }
    407 
    408 int rc_api_process_fetch_game_titles_server_response(rc_api_fetch_game_titles_response_t* response, const rc_api_server_response_t* server_response) {
    409   rc_api_game_title_entry_t* entry;
    410   rc_json_iterator_t iterator;
    411   rc_json_field_t array_field;
    412   int result;
    413 
    414   rc_json_field_t fields[] = {
    415     RC_JSON_NEW_FIELD("Success"),
    416     RC_JSON_NEW_FIELD("Error"),
    417     RC_JSON_NEW_FIELD("Response")
    418   };
    419 
    420   rc_json_field_t entry_fields[] = {
    421     RC_JSON_NEW_FIELD("ID"),
    422     RC_JSON_NEW_FIELD("Title"),
    423     RC_JSON_NEW_FIELD("ImageIcon")
    424   };
    425 
    426   memset(response, 0, sizeof(*response));
    427   rc_buffer_init(&response->response.buffer);
    428 
    429   result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
    430   if (result != RC_OK)
    431     return result;
    432 
    433   if (!rc_json_get_required_array(&response->num_entries, &array_field, &response->response, &fields[2], "Response"))
    434     return RC_MISSING_VALUE;
    435 
    436   if (response->num_entries) {
    437     response->entries = (rc_api_game_title_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_game_title_entry_t));
    438     if (!response->entries)
    439       return RC_OUT_OF_MEMORY;
    440 
    441     memset(&iterator, 0, sizeof(iterator));
    442     iterator.json = array_field.value_start;
    443     iterator.end = array_field.value_end;
    444 
    445     entry = response->entries;
    446     while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {
    447       if (!rc_json_get_required_unum(&entry->id, &response->response, &entry_fields[0], "ID"))
    448         return RC_MISSING_VALUE;
    449       if (!rc_json_get_required_string(&entry->title, &response->response, &entry_fields[1], "Title"))
    450         return RC_MISSING_VALUE;
    451 
    452       /* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */
    453       rc_json_extract_filename(&entry_fields[2]);
    454       if (!rc_json_get_required_string(&entry->image_name, &response->response, &entry_fields[2], "ImageIcon"))
    455         return RC_MISSING_VALUE;
    456 
    457       ++entry;
    458     }
    459   }
    460 
    461   return RC_OK;
    462 }
    463 
    464 void rc_api_destroy_fetch_game_titles_response(rc_api_fetch_game_titles_response_t* response) {
    465   rc_buffer_destroy(&response->response.buffer);
    466 }