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_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, &note_fields[0], "Address"))
     96         return RC_MISSING_VALUE;
     97       note->address = (unsigned)strtol(address_str, NULL, 16);
     98       if (!rc_json_get_required_string(&note->note, &response->response, &note_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(&note->author, &response->response, &note_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 }