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