Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/dep/rcheevos/src/rapi/rc_api_user.c
4255 views
1
#include "rc_api_user.h"
2
#include "rc_api_common.h"
3
#include "rc_api_runtime.h"
4
#include "rc_consoles.h"
5
6
#include "../rc_version.h"
7
8
#include <stdlib.h>
9
#include <string.h>
10
11
/* --- Login --- */
12
13
int rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params) {
14
return rc_api_init_login_request_hosted(request, api_params, &g_host);
15
}
16
17
int rc_api_init_login_request_hosted(rc_api_request_t* request,
18
const rc_api_login_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->username || !*api_params->username)
25
return RC_INVALID_STATE;
26
27
rc_url_builder_init(&builder, &request->buffer, 48);
28
rc_url_builder_append_str_param(&builder, "r", "login2");
29
rc_url_builder_append_str_param(&builder, "u", api_params->username);
30
31
if (api_params->password && api_params->password[0])
32
rc_url_builder_append_str_param(&builder, "p", api_params->password);
33
else if (api_params->api_token && api_params->api_token[0])
34
rc_url_builder_append_str_param(&builder, "t", api_params->api_token);
35
else
36
return RC_INVALID_STATE;
37
38
request->post_data = rc_url_builder_finalize(&builder);
39
request->content_type = RC_CONTENT_TYPE_URLENCODED;
40
41
return builder.result;
42
}
43
44
int rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response) {
45
rc_api_server_response_t response_obj;
46
47
memset(&response_obj, 0, sizeof(response_obj));
48
response_obj.body = server_response;
49
response_obj.body_length = rc_json_get_object_string_length(server_response);
50
51
return rc_api_process_login_server_response(response, &response_obj);
52
}
53
54
int rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response) {
55
int result;
56
rc_json_field_t fields[] = {
57
RC_JSON_NEW_FIELD("Success"),
58
RC_JSON_NEW_FIELD("Error"),
59
RC_JSON_NEW_FIELD("Code"),
60
RC_JSON_NEW_FIELD("User"),
61
RC_JSON_NEW_FIELD("Token"),
62
RC_JSON_NEW_FIELD("Score"),
63
RC_JSON_NEW_FIELD("SoftcoreScore"),
64
RC_JSON_NEW_FIELD("Messages"),
65
RC_JSON_NEW_FIELD("AvatarUrl")
66
};
67
68
memset(response, 0, sizeof(*response));
69
rc_buffer_init(&response->response.buffer);
70
71
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
72
if (result != RC_OK || !response->response.succeeded)
73
return result;
74
75
if (!rc_json_get_required_string(&response->username, &response->response, &fields[3], "User"))
76
return RC_MISSING_VALUE;
77
if (!rc_json_get_required_string(&response->api_token, &response->response, &fields[4], "Token"))
78
return RC_MISSING_VALUE;
79
80
rc_json_get_optional_unum(&response->score, &fields[5], "Score", 0);
81
rc_json_get_optional_unum(&response->score_softcore, &fields[6], "SoftcoreScore", 0);
82
rc_json_get_optional_unum(&response->num_unread_messages, &fields[7], "Messages", 0);
83
84
/* For the highest level of backwards compatibility, we have decided to just send the
85
* display_name back to the client as the "case-corrected username", and allow it to
86
* be used as the Username field for the other APIs. */
87
response->display_name = response->username;
88
89
rc_json_get_optional_string(&response->avatar_url, &response->response, &fields[8], "AvatarUrl", NULL);
90
if (!response->avatar_url)
91
response->avatar_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_USER, response->username);
92
93
return RC_OK;
94
}
95
96
void rc_api_destroy_login_response(rc_api_login_response_t* response) {
97
rc_buffer_destroy(&response->response.buffer);
98
}
99
100
/* --- Start Session --- */
101
102
int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params) {
103
return rc_api_init_start_session_request_hosted(request, api_params, &g_host);
104
}
105
106
int rc_api_init_start_session_request_hosted(rc_api_request_t* request,
107
const rc_api_start_session_request_t* api_params,
108
const rc_api_host_t* host) {
109
rc_api_url_builder_t builder;
110
111
rc_api_url_build_dorequest_url(request, host);
112
113
if (api_params->game_id == 0)
114
return RC_INVALID_STATE;
115
116
rc_url_builder_init(&builder, &request->buffer, 48);
117
if (rc_api_url_build_dorequest(&builder, "startsession", api_params->username, api_params->api_token)) {
118
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
119
120
if (api_params->game_hash && *api_params->game_hash) {
121
rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore);
122
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
123
}
124
125
rc_url_builder_append_str_param(&builder, "l", RCHEEVOS_VERSION_STRING);
126
127
request->post_data = rc_url_builder_finalize(&builder);
128
request->content_type = RC_CONTENT_TYPE_URLENCODED;
129
}
130
131
return builder.result;
132
}
133
134
int rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response) {
135
rc_api_server_response_t response_obj;
136
137
memset(&response_obj, 0, sizeof(response_obj));
138
response_obj.body = server_response;
139
response_obj.body_length = rc_json_get_object_string_length(server_response);
140
141
return rc_api_process_start_session_server_response(response, &response_obj);
142
}
143
144
int rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response) {
145
rc_api_unlock_entry_t* unlock;
146
rc_json_field_t array_field;
147
rc_json_iterator_t iterator;
148
uint32_t timet;
149
int result;
150
151
rc_json_field_t fields[] = {
152
RC_JSON_NEW_FIELD("Success"),
153
RC_JSON_NEW_FIELD("Error"),
154
RC_JSON_NEW_FIELD("Unlocks"),
155
RC_JSON_NEW_FIELD("HardcoreUnlocks"),
156
RC_JSON_NEW_FIELD("ServerNow")
157
};
158
159
rc_json_field_t unlock_entry_fields[] = {
160
RC_JSON_NEW_FIELD("ID"),
161
RC_JSON_NEW_FIELD("When")
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_optional_array(&response->num_unlocks, &array_field, &fields[2], "Unlocks") && response->num_unlocks) {
172
response->unlocks = (rc_api_unlock_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_unlocks * sizeof(rc_api_unlock_entry_t));
173
if (!response->unlocks)
174
return RC_OUT_OF_MEMORY;
175
176
memset(&iterator, 0, sizeof(iterator));
177
iterator.json = array_field.value_start;
178
iterator.end = array_field.value_end;
179
180
unlock = response->unlocks;
181
while (rc_json_get_array_entry_object(unlock_entry_fields, sizeof(unlock_entry_fields) / sizeof(unlock_entry_fields[0]), &iterator)) {
182
if (!rc_json_get_required_unum(&unlock->achievement_id, &response->response, &unlock_entry_fields[0], "ID"))
183
return RC_MISSING_VALUE;
184
if (!rc_json_get_required_unum(&timet, &response->response, &unlock_entry_fields[1], "When"))
185
return RC_MISSING_VALUE;
186
unlock->when = (time_t)timet;
187
188
++unlock;
189
}
190
}
191
192
if (rc_json_get_optional_array(&response->num_hardcore_unlocks, &array_field, &fields[3], "HardcoreUnlocks") && response->num_hardcore_unlocks) {
193
response->hardcore_unlocks = (rc_api_unlock_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_hardcore_unlocks * sizeof(rc_api_unlock_entry_t));
194
if (!response->hardcore_unlocks)
195
return RC_OUT_OF_MEMORY;
196
197
memset(&iterator, 0, sizeof(iterator));
198
iterator.json = array_field.value_start;
199
iterator.end = array_field.value_end;
200
201
unlock = response->hardcore_unlocks;
202
while (rc_json_get_array_entry_object(unlock_entry_fields, sizeof(unlock_entry_fields) / sizeof(unlock_entry_fields[0]), &iterator)) {
203
if (!rc_json_get_required_unum(&unlock->achievement_id, &response->response, &unlock_entry_fields[0], "ID"))
204
return RC_MISSING_VALUE;
205
if (!rc_json_get_required_unum(&timet, &response->response, &unlock_entry_fields[1], "When"))
206
return RC_MISSING_VALUE;
207
unlock->when = (time_t)timet;
208
209
++unlock;
210
}
211
}
212
213
rc_json_get_optional_unum(&timet, &fields[4], "ServerNow", 0);
214
response->server_now = (time_t)timet;
215
216
return RC_OK;
217
}
218
219
void rc_api_destroy_start_session_response(rc_api_start_session_response_t* response) {
220
rc_buffer_destroy(&response->response.buffer);
221
}
222
223
/* --- Fetch User Unlocks --- */
224
225
int rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params) {
226
return rc_api_init_fetch_user_unlocks_request_hosted(request, api_params, &g_host);
227
}
228
229
int rc_api_init_fetch_user_unlocks_request_hosted(rc_api_request_t* request,
230
const rc_api_fetch_user_unlocks_request_t* api_params,
231
const rc_api_host_t* host) {
232
rc_api_url_builder_t builder;
233
234
rc_api_url_build_dorequest_url(request, host);
235
236
rc_url_builder_init(&builder, &request->buffer, 48);
237
if (rc_api_url_build_dorequest(&builder, "unlocks", api_params->username, api_params->api_token)) {
238
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
239
rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore ? 1 : 0);
240
request->post_data = rc_url_builder_finalize(&builder);
241
request->content_type = RC_CONTENT_TYPE_URLENCODED;
242
}
243
244
return builder.result;
245
}
246
247
int rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response) {
248
rc_api_server_response_t response_obj;
249
250
memset(&response_obj, 0, sizeof(response_obj));
251
response_obj.body = server_response;
252
response_obj.body_length = rc_json_get_object_string_length(server_response);
253
254
return rc_api_process_fetch_user_unlocks_server_response(response, &response_obj);
255
}
256
257
int rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response) {
258
int result;
259
rc_json_field_t fields[] = {
260
RC_JSON_NEW_FIELD("Success"),
261
RC_JSON_NEW_FIELD("Error"),
262
RC_JSON_NEW_FIELD("UserUnlocks")
263
/* unused fields
264
RC_JSON_NEW_FIELD("GameID"),
265
RC_JSON_NEW_FIELD("HardcoreMode")
266
* unused fields */
267
};
268
269
memset(response, 0, sizeof(*response));
270
rc_buffer_init(&response->response.buffer);
271
272
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
273
if (result != RC_OK || !response->response.succeeded)
274
return result;
275
276
result = rc_json_get_required_unum_array(&response->achievement_ids, &response->num_achievement_ids, &response->response, &fields[2], "UserUnlocks");
277
return result;
278
}
279
280
void rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response) {
281
rc_buffer_destroy(&response->response.buffer);
282
}
283
284
/* --- Fetch Followed Users --- */
285
286
int rc_api_init_fetch_followed_users_request(rc_api_request_t* request, const rc_api_fetch_followed_users_request_t* api_params) {
287
return rc_api_init_fetch_followed_users_request_hosted(request, api_params, &g_host);
288
}
289
290
int rc_api_init_fetch_followed_users_request_hosted(rc_api_request_t* request,
291
const rc_api_fetch_followed_users_request_t* api_params,
292
const rc_api_host_t* host) {
293
rc_api_url_builder_t builder;
294
295
rc_api_url_build_dorequest_url(request, host);
296
297
rc_url_builder_init(&builder, &request->buffer, 48);
298
if (rc_api_url_build_dorequest(&builder, "getfriendlist", api_params->username, api_params->api_token)) {
299
request->post_data = rc_url_builder_finalize(&builder);
300
request->content_type = RC_CONTENT_TYPE_URLENCODED;
301
}
302
303
return builder.result;
304
}
305
306
int rc_api_process_fetch_followed_users_server_response(rc_api_fetch_followed_users_response_t* response, const rc_api_server_response_t* server_response) {
307
rc_json_field_t array_field;
308
rc_json_iterator_t iterator;
309
rc_api_followed_user_t* user;
310
uint32_t timet;
311
int result;
312
rc_json_field_t fields[] = {
313
RC_JSON_NEW_FIELD("Success"),
314
RC_JSON_NEW_FIELD("Error"),
315
RC_JSON_NEW_FIELD("Friends")
316
};
317
318
rc_json_field_t followed_user_entry_fields[] = {
319
RC_JSON_NEW_FIELD("Friend"),
320
RC_JSON_NEW_FIELD("AvatarUrl"),
321
RC_JSON_NEW_FIELD("RAPoints"),
322
RC_JSON_NEW_FIELD("LastSeen"),
323
RC_JSON_NEW_FIELD("LastSeenTime"),
324
RC_JSON_NEW_FIELD("LastGameId"),
325
RC_JSON_NEW_FIELD("LastGameTitle"),
326
RC_JSON_NEW_FIELD("LastGameIconUrl"),
327
};
328
329
memset(response, 0, sizeof(*response));
330
rc_buffer_init(&response->response.buffer);
331
332
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
333
if (result != RC_OK || !response->response.succeeded)
334
return result;
335
336
if (!rc_json_get_required_array(&response->num_users, &array_field, &response->response, &fields[2], "Friends"))
337
return RC_MISSING_VALUE;
338
339
if (response->num_users) {
340
response->users = (rc_api_followed_user_t*)rc_buffer_alloc(&response->response.buffer, response->num_users * sizeof(rc_api_followed_user_t));
341
if (!response->users)
342
return RC_OUT_OF_MEMORY;
343
344
memset(&iterator, 0, sizeof(iterator));
345
iterator.json = array_field.value_start;
346
iterator.end = array_field.value_end;
347
348
user = response->users;
349
while (rc_json_get_array_entry_object(followed_user_entry_fields, sizeof(followed_user_entry_fields) / sizeof(followed_user_entry_fields[0]), &iterator)) {
350
if (!rc_json_get_required_string(&user->display_name, &response->response, &followed_user_entry_fields[0], "Friend"))
351
return RC_MISSING_VALUE;
352
if (!rc_json_get_required_string(&user->avatar_url, &response->response, &followed_user_entry_fields[1], "AvatarUrl"))
353
return RC_MISSING_VALUE;
354
if (!rc_json_get_required_unum(&user->score, &response->response, &followed_user_entry_fields[2], "RAPoints"))
355
return RC_MISSING_VALUE;
356
357
rc_json_get_optional_string(&user->recent_activity.description, &response->response, &followed_user_entry_fields[3], "LastSeen", NULL);
358
359
rc_json_get_optional_unum(&timet, &followed_user_entry_fields[4], "LastSeenTime", 0);
360
user->recent_activity.when = (time_t)timet;
361
362
rc_json_get_optional_unum(&user->recent_activity.context_id, &followed_user_entry_fields[5], "LastGameId", 0);
363
rc_json_get_optional_string(&user->recent_activity.context, &response->response, &followed_user_entry_fields[6], "LastGameTitle", NULL);
364
rc_json_get_optional_string(&user->recent_activity.context_image_url, &response->response, &followed_user_entry_fields[7], "LastGameIconUrl", NULL);
365
366
++user;
367
}
368
}
369
370
return RC_OK;
371
}
372
373
void rc_api_destroy_fetch_followed_users_response(rc_api_fetch_followed_users_response_t* response) {
374
rc_buffer_destroy(&response->response.buffer);
375
}
376
377
/* --- Fetch All Progress --- */
378
379
int rc_api_init_fetch_all_user_progress_request(rc_api_request_t* request,
380
const rc_api_fetch_all_user_progress_request_t* api_params)
381
{
382
return rc_api_init_fetch_all_user_progress_request_hosted(request, api_params, &g_host);
383
}
384
385
int rc_api_init_fetch_all_user_progress_request_hosted(rc_api_request_t* request,
386
const rc_api_fetch_all_user_progress_request_t* api_params,
387
const rc_api_host_t* host)
388
{
389
rc_api_url_builder_t builder;
390
391
rc_api_url_build_dorequest_url(request, host);
392
393
if (api_params->console_id == RC_CONSOLE_UNKNOWN)
394
return RC_INVALID_STATE;
395
396
rc_url_builder_init(&builder, &request->buffer, 48);
397
if (rc_api_url_build_dorequest(&builder, "allprogress", api_params->username, api_params->api_token))
398
{
399
rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);
400
request->post_data = rc_url_builder_finalize(&builder);
401
request->content_type = RC_CONTENT_TYPE_URLENCODED;
402
}
403
404
return builder.result;
405
}
406
407
int rc_api_process_fetch_all_user_progress_server_response(rc_api_fetch_all_user_progress_response_t* response,
408
const rc_api_server_response_t* server_response)
409
{
410
rc_api_all_user_progress_entry_t* entry;
411
int result;
412
413
rc_json_field_t fields[] = {
414
RC_JSON_NEW_FIELD("Success"),
415
RC_JSON_NEW_FIELD("Error"),
416
RC_JSON_NEW_FIELD("Response"),
417
};
418
419
rc_json_field_t entry_fields[] = {
420
RC_JSON_NEW_FIELD("Achievements"),
421
RC_JSON_NEW_FIELD("Unlocked"),
422
RC_JSON_NEW_FIELD("UnlockedHardcore"),
423
};
424
425
memset(response, 0, sizeof(*response));
426
rc_buffer_init(&response->response.buffer);
427
428
result =
429
rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
430
if (result != RC_OK || !response->response.succeeded)
431
return result;
432
433
if (!fields[2].value_start) {
434
/* call rc_json_get_required_object to generate the error message */
435
rc_json_get_required_object(NULL, 0, &response->response, &fields[2], "Response");
436
return RC_MISSING_VALUE;
437
}
438
439
response->num_entries = fields[2].array_size;
440
441
if (response->num_entries > 0) {
442
rc_json_iterator_t iterator;
443
rc_json_field_t field;
444
char* end;
445
446
rc_buffer_reserve(&response->response.buffer, response->num_entries * sizeof(rc_api_all_user_progress_entry_t));
447
448
response->entries = (rc_api_all_user_progress_entry_t*)rc_buffer_alloc(
449
&response->response.buffer, response->num_entries * sizeof(rc_api_all_user_progress_entry_t));
450
if (!response->entries)
451
return RC_OUT_OF_MEMORY;
452
453
memset(&iterator, 0, sizeof(iterator));
454
iterator.json = fields[2].value_start;
455
iterator.end = fields[2].value_end;
456
457
entry = response->entries;
458
while (rc_json_get_next_object_field(&iterator, &field))
459
{
460
entry->game_id = strtol(field.name, &end, 10);
461
462
field.name = "";
463
if (!rc_json_get_required_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]),
464
&response->response, &field, ""))
465
{
466
return RC_MISSING_VALUE;
467
}
468
469
rc_json_get_optional_unum(&entry->num_achievements, &entry_fields[0], "Achievements", 0);
470
rc_json_get_optional_unum(&entry->num_unlocked_achievements, &entry_fields[1], "Unlocked", 0);
471
rc_json_get_optional_unum(&entry->num_unlocked_achievements_hardcore, &entry_fields[2], "UnlockedHardcore", 0);
472
473
++entry;
474
}
475
}
476
477
return RC_OK;
478
}
479
480
void rc_api_destroy_fetch_all_user_progress_response(rc_api_fetch_all_user_progress_response_t* response)
481
{
482
rc_buffer_destroy(&response->response.buffer);
483
}
484
485