Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/dep/rcheevos/src/rc_client_external.c
7513 views
1
#include "rc_client_external.h"
2
3
#include "rc_client_external_versions.h"
4
#include "rc_client_internal.h"
5
6
#include "rc_api_runtime.h"
7
8
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
9
10
#define RC_CONVERSION_FILL(obj, obj_type, src_type) memset((uint8_t*)obj + sizeof(src_type), 0, sizeof(obj_type) - sizeof(src_type))
11
12
/* https://media.retroachievements.org/Badge/123456_lock.png is 58 with null terminator */
13
#define RC_CLIENT_IMAGE_URL_BUFFER_SIZE 64
14
/* https://media.retroachievements.org/UserPic/TwentyCharUserNameXX.png is 69 with null terminator */
15
#define RC_CLIENT_USER_IMAGE_URL_BUFFER_SIZE 80
16
17
typedef struct rc_client_external_conversions_t {
18
rc_client_user_t user;
19
rc_client_game_t game;
20
rc_client_subset_t subsets[4];
21
rc_client_achievement_t achievements[16];
22
char user_avatar_url[RC_CLIENT_USER_IMAGE_URL_BUFFER_SIZE];
23
char game_badge_url[RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
24
char subset_badge_url[4][RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
25
char achievement_badge_url[16][RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
26
char achievement_badge_locked_url[16][RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
27
uint32_t next_subset_index;
28
uint32_t next_achievement_index;
29
} rc_client_external_conversions_t;
30
31
static const char* rc_client_external_build_avatar_url(char buffer[], size_t buffer_size, uint32_t image_type, const char* image_name)
32
{
33
rc_api_fetch_image_request_t image_request;
34
rc_api_request_t request;
35
int result;
36
37
memset(&image_request, 0, sizeof(image_request));
38
image_request.image_type = image_type;
39
image_request.image_name = image_name;
40
41
result = rc_api_init_fetch_image_request(&request, &image_request);
42
if (result != RC_OK)
43
return NULL;
44
45
snprintf(buffer, buffer_size, "%s", request.url);
46
return buffer;
47
}
48
49
static void rc_client_external_conversions_init(const rc_client_t* client)
50
{
51
if (!client->state.external_client_conversions) {
52
rc_client_t* mutable_client = (rc_client_t*)client;
53
rc_client_external_conversions_t* conversions = (rc_client_external_conversions_t*)
54
rc_buffer_alloc(&mutable_client->state.buffer, sizeof(rc_client_external_conversions_t));
55
56
memset(conversions, 0, sizeof(*conversions));
57
58
mutable_client->state.external_client_conversions = conversions;
59
}
60
}
61
62
const rc_client_user_t* rc_client_external_convert_v1_user(const rc_client_t* client, const rc_client_user_t* v1_user)
63
{
64
rc_client_user_t* converted;
65
66
if (!v1_user)
67
return NULL;
68
69
rc_client_external_conversions_init(client);
70
71
converted = &client->state.external_client_conversions->user;
72
memcpy(converted, v1_user, sizeof(v1_rc_client_user_t));
73
RC_CONVERSION_FILL(converted, rc_client_user_t, v1_rc_client_user_t);
74
75
converted->avatar_url = rc_client_external_build_avatar_url(
76
client->state.external_client_conversions->user_avatar_url,
77
sizeof(client->state.external_client_conversions->user_avatar_url),
78
RC_IMAGE_TYPE_USER, v1_user->username);
79
80
return converted;
81
}
82
83
const rc_client_game_t* rc_client_external_convert_v1_game(const rc_client_t* client, const rc_client_game_t* v1_game)
84
{
85
rc_client_game_t* converted;
86
87
if (!v1_game)
88
return NULL;
89
90
rc_client_external_conversions_init(client);
91
92
converted = &client->state.external_client_conversions->game;
93
memcpy(converted, v1_game, sizeof(v1_rc_client_game_t));
94
RC_CONVERSION_FILL(converted, rc_client_game_t, v1_rc_client_game_t);
95
96
converted->badge_url = rc_client_external_build_avatar_url(
97
client->state.external_client_conversions->game_badge_url,
98
sizeof(client->state.external_client_conversions->game_badge_url),
99
RC_IMAGE_TYPE_GAME, v1_game->badge_name);
100
101
return converted;
102
}
103
104
const rc_client_subset_t* rc_client_external_convert_v1_subset(const rc_client_t* client, const rc_client_subset_t* v1_subset)
105
{
106
rc_client_subset_t* converted = NULL;
107
char* badge_url = NULL;
108
const uint32_t num_subsets = sizeof(client->state.external_client_conversions->subsets) / sizeof(client->state.external_client_conversions->subsets[0]);
109
uint32_t index;
110
111
if (!v1_subset)
112
return NULL;
113
114
rc_client_external_conversions_init(client);
115
116
for (index = 0; index < num_subsets; ++index) {
117
if (client->state.external_client_conversions->subsets[index].id == v1_subset->id) {
118
converted = &client->state.external_client_conversions->subsets[index];
119
badge_url = client->state.external_client_conversions->subset_badge_url[index];
120
break;
121
}
122
}
123
124
if (!converted) {
125
index = client->state.external_client_conversions->next_subset_index;
126
converted = &client->state.external_client_conversions->subsets[index];
127
badge_url = client->state.external_client_conversions->subset_badge_url[client->state.external_client_conversions->next_subset_index];
128
client->state.external_client_conversions->next_subset_index = (index + 1) % num_subsets;
129
}
130
131
memcpy(converted, v1_subset, sizeof(v1_rc_client_subset_t));
132
RC_CONVERSION_FILL(converted, rc_client_subset_t, v1_rc_client_subset_t);
133
134
converted->badge_url = rc_client_external_build_avatar_url(badge_url,
135
sizeof(client->state.external_client_conversions->subset_badge_url[0]),
136
RC_IMAGE_TYPE_GAME, v1_subset->badge_name);
137
138
return converted;
139
}
140
141
const rc_client_achievement_t* rc_client_external_convert_v1_achievement(const rc_client_t* client, const rc_client_achievement_t* v1_achievement)
142
{
143
rc_client_achievement_t* converted = NULL;
144
char* badge_url = NULL;
145
char* badge_locked_url = NULL;
146
const uint32_t num_achievements = sizeof(client->state.external_client_conversions->achievements) / sizeof(client->state.external_client_conversions->achievements[0]);
147
uint32_t index;
148
149
if (!v1_achievement)
150
return NULL;
151
152
rc_client_external_conversions_init(client);
153
154
for (index = 0; index < num_achievements; ++index) {
155
if (client->state.external_client_conversions->achievements[index].id == v1_achievement->id) {
156
converted = &client->state.external_client_conversions->achievements[index];
157
badge_url = client->state.external_client_conversions->achievement_badge_url[index];
158
badge_locked_url = client->state.external_client_conversions->achievement_badge_locked_url[index];
159
break;
160
}
161
}
162
163
if (!converted) {
164
index = client->state.external_client_conversions->next_achievement_index;
165
converted = &client->state.external_client_conversions->achievements[index];
166
badge_url = client->state.external_client_conversions->achievement_badge_url[index];
167
badge_locked_url = client->state.external_client_conversions->achievement_badge_locked_url[index];
168
client->state.external_client_conversions->next_achievement_index = (index + 1) % num_achievements;
169
}
170
171
memcpy(converted, v1_achievement, sizeof(v1_rc_client_achievement_t));
172
RC_CONVERSION_FILL(converted, rc_client_achievement_t, v1_rc_client_achievement_t);
173
174
converted->badge_url = rc_client_external_build_avatar_url(badge_url,
175
sizeof(client->state.external_client_conversions->achievement_badge_url[0]),
176
RC_IMAGE_TYPE_ACHIEVEMENT, v1_achievement->badge_name);
177
converted->badge_locked_url = rc_client_external_build_avatar_url(badge_locked_url,
178
sizeof(client->state.external_client_conversions->achievement_badge_locked_url[0]),
179
RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, v1_achievement->badge_name);
180
181
return converted;
182
}
183
184
typedef struct rc_client_achievement_list_wrapper_t {
185
rc_client_achievement_list_info_t info;
186
rc_client_achievement_list_t* source_list;
187
rc_client_achievement_t* achievements;
188
rc_client_achievement_t** achievements_pointers;
189
char* badge_url_buffer;
190
} rc_client_achievement_list_wrapper_t;
191
192
static void rc_client_destroy_achievement_list_wrapper(rc_client_achievement_list_info_t* info)
193
{
194
rc_client_achievement_list_wrapper_t* wrapper = (rc_client_achievement_list_wrapper_t*)info;
195
196
if (wrapper->achievements)
197
free(wrapper->achievements);
198
if (wrapper->achievements_pointers)
199
free(wrapper->achievements_pointers);
200
if (wrapper->info.public_.buckets)
201
free((void*)wrapper->info.public_.buckets);
202
if (wrapper->badge_url_buffer)
203
free(wrapper->badge_url_buffer);
204
205
rc_client_destroy_achievement_list(wrapper->source_list);
206
207
free(wrapper);
208
}
209
210
rc_client_achievement_list_t* rc_client_external_convert_v1_achievement_list(const rc_client_t* client, rc_client_achievement_list_t* v1_achievement_list)
211
{
212
rc_client_achievement_list_wrapper_t* new_list;
213
(void)client;
214
215
if (!v1_achievement_list)
216
return NULL;
217
218
new_list = (rc_client_achievement_list_wrapper_t*)calloc(1, sizeof(*new_list));
219
if (!new_list)
220
return NULL;
221
222
new_list->source_list = v1_achievement_list;
223
new_list->info.destroy_func = rc_client_destroy_achievement_list_wrapper;
224
225
if (v1_achievement_list->num_buckets) {
226
const v1_rc_client_achievement_bucket_t* src_bucket = (const v1_rc_client_achievement_bucket_t*)&v1_achievement_list->buckets[0];
227
const v1_rc_client_achievement_bucket_t* stop_bucket = src_bucket + v1_achievement_list->num_buckets;
228
rc_client_achievement_bucket_t* bucket;
229
uint32_t num_achievements = 0;
230
char* badge_url = NULL;
231
232
new_list->info.public_.buckets = bucket = (rc_client_achievement_bucket_t*)calloc(v1_achievement_list->num_buckets, sizeof(*new_list->info.public_.buckets));
233
if (!new_list->info.public_.buckets)
234
return (rc_client_achievement_list_t*)new_list;
235
236
for (; src_bucket < stop_bucket; src_bucket++)
237
num_achievements += src_bucket->num_achievements;
238
239
if (num_achievements) {
240
new_list->achievements = (rc_client_achievement_t*)calloc(num_achievements, sizeof(*new_list->achievements));
241
new_list->achievements_pointers = (rc_client_achievement_t**)malloc(num_achievements * sizeof(rc_client_achievement_t*));
242
new_list->badge_url_buffer = badge_url = (char*)malloc(num_achievements * 2 * RC_CLIENT_IMAGE_URL_BUFFER_SIZE);
243
if (!new_list->achievements || !new_list->achievements_pointers || !new_list->badge_url_buffer)
244
return (rc_client_achievement_list_t*)new_list;
245
}
246
247
num_achievements = 0;
248
src_bucket = (const v1_rc_client_achievement_bucket_t*)&v1_achievement_list->buckets[0];
249
for (; src_bucket < stop_bucket; src_bucket++, bucket++) {
250
memcpy(bucket, src_bucket, sizeof(*src_bucket));
251
252
if (src_bucket->num_achievements) {
253
const v1_rc_client_achievement_t** src_achievement = (const v1_rc_client_achievement_t**)src_bucket->achievements;
254
const v1_rc_client_achievement_t** stop_achievement = src_achievement + src_bucket->num_achievements;
255
rc_client_achievement_t** achievement = &new_list->achievements_pointers[num_achievements];
256
257
bucket->achievements = (const rc_client_achievement_t**)achievement;
258
259
for (; src_achievement < stop_achievement; ++src_achievement, ++achievement) {
260
*achievement = &new_list->achievements[num_achievements++];
261
memcpy(*achievement, *src_achievement, sizeof(**src_achievement));
262
263
(*achievement)->badge_url = rc_client_external_build_avatar_url(badge_url,
264
sizeof(client->state.external_client_conversions->achievement_badge_url[0]),
265
RC_IMAGE_TYPE_ACHIEVEMENT, (*achievement)->badge_name);
266
badge_url += RC_CLIENT_IMAGE_URL_BUFFER_SIZE;
267
(*achievement)->badge_locked_url = rc_client_external_build_avatar_url(badge_url,
268
sizeof(client->state.external_client_conversions->achievement_badge_locked_url[0]),
269
RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, (*achievement)->badge_name);
270
badge_url += RC_CLIENT_IMAGE_URL_BUFFER_SIZE;
271
}
272
}
273
}
274
275
new_list->info.public_.num_buckets = v1_achievement_list->num_buckets;
276
}
277
278
return (rc_client_achievement_list_t*)new_list;
279
}
280
281
#endif /* RC_CLIENT_SUPPORTS_EXTERNAL */
282
283