1 /* RetroArch - A frontend for libretro.
2 * Copyright (C) 2015-2016 - Andre Leiradella
3 *
4 * RetroArch is free software: you can redistribute it and/or modify it under the terms
5 * of the GNU General Public License as published by the Free Software Found-
6 * ation, either version 3 of the License, or (at your option) any later version.
7 *
8 * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
10 * PURPOSE. See the GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License along with RetroArch.
13 * If not, see <http://www.gnu.org/licenses/>.
14 */
15
16 #include <string.h>
17 #include <ctype.h>
18
19 #include <file/file_path.h>
20 #include <string/stdstring.h>
21 #include <streams/interface_stream.h>
22 #include <streams/file_stream.h>
23 #include <features/features_cpu.h>
24 #include <formats/cdfs.h>
25 #include <formats/m3u_file.h>
26 #include <compat/strl.h>
27 #include <retro_miscellaneous.h>
28 #include <retro_math.h>
29 #include <net/net_http.h>
30 #include <libretro.h>
31
32 #ifdef HAVE_CONFIG_H
33 #include "../config.h"
34 #endif
35
36 #ifdef HAVE_GFX_WIDGETS
37 #include "../gfx/gfx_widgets.h"
38 #endif
39
40 #ifdef HAVE_THREADS
41 #include <rthreads/rthreads.h>
42 #endif
43
44 #ifdef HAVE_DISCORD
45 #include "../network/discord.h"
46 #endif
47
48 #ifdef HAVE_CHEATS
49 #include "../cheat_manager.h"
50 #endif
51
52 #ifdef HAVE_CHD
53 #include "streams/chd_stream.h"
54 #endif
55
56 #include "cheevos.h"
57 #include "cheevos_locals.h"
58 #include "cheevos_parser.h"
59
60 #include "../file_path_special.h"
61 #include "../paths.h"
62 #include "../command.h"
63 #include "../dynamic.h"
64 #include "../configuration.h"
65 #include "../performance_counters.h"
66 #include "../msg_hash.h"
67 #include "../retroarch.h"
68 #include "../core.h"
69 #include "../core_option_manager.h"
70 #include "../version.h"
71
72 #include "../frontend/frontend_driver.h"
73 #include "../network/net_http_special.h"
74 #include "../tasks/tasks_internal.h"
75
76 #include "../deps/rcheevos/include/rc_runtime.h"
77 #include "../deps/rcheevos/include/rc_url.h"
78 #include "../deps/rcheevos/include/rc_hash.h"
79 #include "../deps/rcheevos/src/rcheevos/rc_libretro.h"
80
81 /* Define this macro to prevent cheevos from being deactivated. */
82 #undef CHEEVOS_DONT_DEACTIVATE
83
84 /* Define this macro to load a JSON file from disk instead of downloading
85 * from retroachievements.org. */
86 #undef CHEEVOS_JSON_OVERRIDE
87
88 /* Define this macro with a string to save the JSON file to disk with
89 * that name. */
90 #undef CHEEVOS_SAVE_JSON
91
92 /* Define this macro to log URLs. */
93 #undef CHEEVOS_LOG_URLS
94
95 /* Define this macro to have the password and token logged. THIS WILL DISCLOSE
96 * THE USER'S PASSWORD, TAKE CARE! */
97 #undef CHEEVOS_LOG_PASSWORD
98
99 /* Define this macro to log downloaded badge images. */
100 #undef CHEEVOS_LOG_BADGES
101
102 /* Define this macro to capture how long it takes to generate a hash */
103 #undef CHEEVOS_TIME_HASH
104
105 /* Number of usecs to wait between posting rich presence to the site. */
106 /* Keep consistent with SERVER_PING_FREQUENCY from RAIntegration. */
107 #define CHEEVOS_PING_FREQUENCY 2 * 60 * 1000000
108
109 enum rcheevos_async_io_type
110 {
111 CHEEVOS_ASYNC_RICHPRESENCE = 0,
112 CHEEVOS_ASYNC_AWARD_ACHIEVEMENT,
113 CHEEVOS_ASYNC_SUBMIT_LBOARD
114 };
115
116 typedef struct rcheevos_async_io_request
117 {
118 const char* success_message;
119 const char* failure_message;
120 int id;
121 int value;
122 int attempt_count;
123 char user_agent[256];
124 char type;
125 char hardcore;
126 } rcheevos_async_io_request;
127
128 static rcheevos_locals_t rcheevos_locals =
129 {
130 {0}, /* runtime */
131 {0}, /* patchdata */
132 {{0}},/* memory */
133 NULL, /* task */
134 #ifdef HAVE_THREADS
135 NULL, /* task_lock */
136 CMD_EVENT_NONE, /* queued_command */
137 #endif
138 {0}, /* token */
139 "N/A",/* hash */
140 "", /* user_agent_prefix */
141 #ifdef HAVE_MENU
142 NULL, /* menuitems */
143 0, /* menuitem_capacity */
144 0, /* menuitem_count */
145 #endif
146 false,/* hardcore_active */
147 false,/* loaded */
148 true, /* core_supports */
149 false,/* leaderboards_enabled */
150 false,/* leaderboard_notifications */
151 false /* leaderboard_trackers */
152 };
153
get_rcheevos_locals(void)154 rcheevos_locals_t* get_rcheevos_locals(void)
155 {
156 return &rcheevos_locals;
157 }
158
159 #ifdef HAVE_THREADS
160 #define CHEEVOS_LOCK(l) do { slock_lock(l); } while (0)
161 #define CHEEVOS_UNLOCK(l) do { slock_unlock(l); } while (0)
162 #else
163 #define CHEEVOS_LOCK(l)
164 #define CHEEVOS_UNLOCK(l)
165 #endif
166
167 #define CHEEVOS_MB(x) ((x) * 1024 * 1024)
168
169 /* Forward declaration */
170 static void rcheevos_async_task_callback(
171 retro_task_t* task, void* task_data, void* user_data, const char* error);
172 static void rcheevos_async_submit_lboard(rcheevos_locals_t *locals,
173 rcheevos_async_io_request* request);
174 static void rcheevos_validate_memrefs(rcheevos_locals_t* locals);
175
176 /*****************************************************************************
177 Supporting functions.
178 *****************************************************************************/
179
180 #ifndef CHEEVOS_VERBOSE
rcheevos_log(const char * fmt,...)181 void rcheevos_log(const char *fmt, ...)
182 {
183 (void)fmt;
184 }
185 #endif
186
append_no_spaces(char * buffer,char * stop,const char * text)187 static int append_no_spaces(char* buffer, char* stop, const char* text)
188 {
189 char* ptr = buffer;
190
191 while (ptr < stop && *text)
192 {
193 if (*text == ' ')
194 {
195 *ptr++ = '_';
196 ++text;
197 }
198 else
199 {
200 *ptr++ = *text++;
201 }
202 }
203
204 *ptr = '\0';
205 return (ptr - buffer);
206 }
207
rcheevos_get_user_agent(rcheevos_locals_t * locals,char * buffer,size_t len)208 static void rcheevos_get_user_agent(
209 rcheevos_locals_t *locals,
210 char *buffer, size_t len)
211 {
212 struct retro_system_info *system = runloop_get_libretro_system_info();
213 char* ptr;
214
215 /* if we haven't calculated the non-changing portion yet, do so now [retroarch version + os version] */
216 if (!locals->user_agent_prefix[0])
217 {
218 const frontend_ctx_driver_t *frontend = frontend_get_ptr();
219 int major, minor;
220 char tmp[64];
221
222 if (frontend && frontend->get_os)
223 {
224 frontend->get_os(tmp, sizeof(tmp), &major, &minor);
225 snprintf(locals->user_agent_prefix, sizeof(locals->user_agent_prefix),
226 "RetroArch/%s (%s %d.%d)", PACKAGE_VERSION, tmp, major, minor);
227 }
228 else
229 {
230 snprintf(locals->user_agent_prefix, sizeof(locals->user_agent_prefix),
231 "RetroArch/%s", PACKAGE_VERSION);
232 }
233 }
234
235 /* append the non-changing portion */
236 ptr = buffer + strlcpy(buffer, locals->user_agent_prefix, len);
237
238 /* if a core is loaded, append its information */
239 if (system && !string_is_empty(system->library_name))
240 {
241 char* stop = buffer + len - 1;
242 const char* path = path_get(RARCH_PATH_CORE);
243 *ptr++ = ' ';
244
245 if (!string_is_empty(path))
246 {
247 append_no_spaces(ptr, stop, path_basename(path));
248 path_remove_extension(ptr);
249 ptr += strlen(ptr);
250 }
251 else
252 {
253 ptr += append_no_spaces(ptr, stop, system->library_name);
254 }
255
256 if (system->library_version)
257 {
258 *ptr++ = '/';
259 ptr += append_no_spaces(ptr, stop, system->library_version);
260 }
261 }
262
263 *ptr = '\0';
264 }
265
266 #ifdef CHEEVOS_LOG_URLS
rcheevos_filter_url_param(char * url,char * param)267 static void rcheevos_filter_url_param(char* url, char* param)
268 {
269 char *next;
270 size_t param_len = strlen(param);
271 char *start = strchr(url, '?');
272 if (!start)
273 start = url;
274 else
275 ++start;
276
277 do
278 {
279 next = strchr(start, '&');
280
281 if (start[param_len] == '=' && memcmp(start, param, param_len) == 0)
282 {
283 if (next)
284 strcpy_literal(start, next + 1);
285 else if (start > url)
286 start[-1] = '\0';
287 else
288 *start = '\0';
289
290 return;
291 }
292
293 if (!next)
294 return;
295
296 start = next + 1;
297 } while (1);
298 }
299 #endif
300
rcheevos_log_url(const char * api,const char * url)301 static void rcheevos_log_url(const char* api, const char* url)
302 {
303 #ifdef CHEEVOS_LOG_URLS
304 #ifdef CHEEVOS_LOG_PASSWORD
305 CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s\n", api, url);
306 #else
307 char copy[256];
308 strlcpy(copy, url, sizeof(copy));
309 rcheevos_filter_url_param(copy, "p");
310 rcheevos_filter_url_param(copy, "t");
311 CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s\n", api, copy);
312 #endif
313 #else
314 (void)api;
315 (void)url;
316 #endif
317 }
318
rcheevos_log_post_url(const char * api,const char * url,const char * post)319 static void rcheevos_log_post_url(
320 const char* api,
321 const char* url,
322 const char* post)
323 {
324 #ifdef CHEEVOS_LOG_URLS
325 #ifdef CHEEVOS_LOG_PASSWORD
326 if (post && post[0])
327 CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s&%s\n", api, url, post);
328 else
329 CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s\n", api, url);
330 #else
331 if (post && post[0])
332 {
333 char post_copy[2048];
334 strlcpy(post_copy, post, sizeof(post_copy));
335 rcheevos_filter_url_param(post_copy, "p");
336 rcheevos_filter_url_param(post_copy, "t");
337
338 if (post_copy[0])
339 CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s&%s\n", api, url, post_copy);
340 else
341 CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s\n", api, url);
342 }
343 else
344 {
345 CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s\n", api, url);
346 }
347 #endif
348 #else
349 (void)api;
350 (void)url;
351 (void)post;
352 #endif
353 }
354
rcheevos_achievement_disabled(rcheevos_racheevo_t * cheevo,unsigned address)355 static void rcheevos_achievement_disabled(rcheevos_racheevo_t* cheevo, unsigned address)
356 {
357 if (!cheevo)
358 return;
359
360 CHEEVOS_ERR(RCHEEVOS_TAG "Achievement %u disabled (invalid address %06X): %s\n",
361 cheevo->id, address, cheevo->title);
362 CHEEVOS_FREE(cheevo->memaddr);
363 cheevo->memaddr = NULL;
364 }
365
rcheevos_lboard_disabled(rcheevos_ralboard_t * lboard,unsigned address)366 static void rcheevos_lboard_disabled(rcheevos_ralboard_t* lboard, unsigned address)
367 {
368 if (!lboard)
369 return;
370
371 CHEEVOS_ERR(RCHEEVOS_TAG "Leaderboard %u disabled (invalid address %06X): %s\n",
372 lboard->id, address, lboard->title);
373 CHEEVOS_FREE(lboard->mem);
374 lboard->mem = NULL;
375 }
376
rcheevos_handle_log_message(const char * message)377 static void rcheevos_handle_log_message(const char* message)
378 {
379 CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", message);
380 }
381
rcheevos_get_core_memory_info(unsigned id,rc_libretro_core_memory_info_t * info)382 static void rcheevos_get_core_memory_info(unsigned id, rc_libretro_core_memory_info_t* info)
383 {
384 retro_ctx_memory_info_t ctx_info;
385 if (!info)
386 return;
387
388 ctx_info.id = id;
389 if (core_get_memory(&ctx_info))
390 {
391 info->data = (unsigned char*)ctx_info.data;
392 info->size = ctx_info.size;
393 }
394 else
395 {
396 info->data = NULL;
397 info->size = 0;
398 }
399 }
400
rcheevos_init_memory(rcheevos_locals_t * locals)401 static int rcheevos_init_memory(rcheevos_locals_t* locals)
402 {
403 rarch_system_info_t* system = runloop_get_system_info();
404 rarch_memory_map_t* mmaps = &system->mmaps;
405 struct retro_memory_descriptor descriptors[64];
406 struct retro_memory_map mmap;
407 unsigned i;
408
409 mmap.descriptors = &descriptors[0];
410 mmap.num_descriptors = sizeof(descriptors) / sizeof(descriptors[0]);
411 if (mmaps->num_descriptors < mmap.num_descriptors)
412 mmap.num_descriptors = mmaps->num_descriptors;
413
414 /* RetroArch wraps the retro_memory_descriptor's in rarch_memory_descriptor_t's, pull them back out */
415 for (i = 0; i < mmap.num_descriptors; ++i)
416 memcpy(&descriptors[i], &mmaps->descriptors[i].core, sizeof(descriptors[0]));
417
418 rc_libretro_init_verbose_message_callback(rcheevos_handle_log_message);
419 return rc_libretro_memory_init(&locals->memory, &mmap,
420 rcheevos_get_core_memory_info, locals->patchdata.console_id);
421 }
422
rcheevos_patch_address(unsigned address)423 uint8_t* rcheevos_patch_address(unsigned address)
424 {
425 if (rcheevos_locals.memory.count == 0)
426 {
427 /* memory map was not previously initialized (no achievements for this game?) try now */
428 rcheevos_init_memory(&rcheevos_locals);
429 }
430
431 return rc_libretro_memory_find(&rcheevos_locals.memory, address);
432 }
433
rcheevos_peek(unsigned address,unsigned num_bytes,void * ud)434 static unsigned rcheevos_peek(unsigned address, unsigned num_bytes, void* ud)
435 {
436 uint8_t* data = rc_libretro_memory_find(&rcheevos_locals.memory, address);
437 if (data)
438 {
439 switch (num_bytes)
440 {
441 case 4:
442 return (data[3] << 24) | (data[2] << 16) |
443 (data[1] << 8) | (data[0]);
444 case 3:
445 return (data[2] << 16) | (data[1] << 8) | (data[0]);
446 case 2:
447 return (data[1] << 8) | (data[0]);
448 case 1:
449 return data[0];
450 }
451 }
452
453 return 0;
454 }
455
rcheevos_async_award_achievement(rcheevos_locals_t * locals,rcheevos_async_io_request * request)456 static void rcheevos_async_award_achievement(
457 rcheevos_locals_t *locals,
458 rcheevos_async_io_request* request)
459 {
460 char buffer[256];
461 settings_t *settings = config_get_ptr();
462 int ret = rc_url_award_cheevo(buffer, sizeof(buffer),
463 settings->arrays.cheevos_username,
464 locals->token,
465 request->id,
466 request->hardcore,
467 locals->hash);
468
469 if (ret != 0)
470 {
471 CHEEVOS_ERR(RCHEEVOS_TAG "Buffer too small to create URL\n");
472 free(request);
473 return;
474 }
475
476 rcheevos_log_url("rc_url_award_cheevo", buffer);
477 task_push_http_transfer_with_user_agent(buffer, true, NULL,
478 request->user_agent, rcheevos_async_task_callback, request);
479
480 #ifdef HAVE_AUDIOMIXER
481 if (settings->bools.cheevos_unlock_sound_enable)
482 audio_driver_mixer_play_menu_sound(
483 AUDIO_MIXER_SYSTEM_SLOT_ACHIEVEMENT_UNLOCK);
484 #endif
485 }
486
rcheevos_async_send_rich_presence(rcheevos_locals_t * locals,rcheevos_async_io_request * request)487 static retro_time_t rcheevos_async_send_rich_presence(
488 rcheevos_locals_t *locals,
489 rcheevos_async_io_request* request)
490 {
491 char url[256], post_data[1024];
492 char buffer[256] = "";
493 const settings_t *settings = config_get_ptr();
494 const char *cheevos_username = settings->arrays.cheevos_username;
495 const bool cheevos_richpresence_enable = settings->bools.cheevos_richpresence_enable;
496 int ret;
497
498 if (cheevos_richpresence_enable)
499 rcheevos_get_richpresence(buffer, sizeof(buffer));
500
501 ret = rc_url_ping(url, sizeof(url), post_data, sizeof(post_data),
502 cheevos_username, locals->token, locals->patchdata.game_id, buffer);
503
504 if (ret < 0)
505 {
506 CHEEVOS_ERR(RCHEEVOS_TAG "buffer too small to create URL\n");
507 }
508 else
509 {
510 rcheevos_log_post_url("rc_url_ping", url, post_data);
511
512 rcheevos_get_user_agent(locals,
513 request->user_agent, sizeof(request->user_agent));
514 task_push_http_post_transfer_with_user_agent(url, post_data, true, "POST", request->user_agent, NULL, NULL);
515 }
516
517 #ifdef HAVE_DISCORD
518 if (settings->bools.discord_enable && discord_is_ready())
519 discord_update(DISCORD_PRESENCE_RETROACHIEVEMENTS);
520 #endif
521
522 /* Update rich presence every two minutes */
523 if (cheevos_richpresence_enable)
524 return cpu_features_get_time_usec() + CHEEVOS_PING_FREQUENCY;
525
526 /* Send ping every four minutes */
527 return cpu_features_get_time_usec() + CHEEVOS_PING_FREQUENCY * 2;
528 }
529
rcheevos_async_task_handler(retro_task_t * task)530 static void rcheevos_async_task_handler(retro_task_t* task)
531 {
532 rcheevos_async_io_request* request = (rcheevos_async_io_request*)
533 task->user_data;
534
535 switch (request->type)
536 {
537 case CHEEVOS_ASYNC_RICHPRESENCE:
538 /* update the task to fire again in two minutes */
539 if (request->id == (int)rcheevos_locals.patchdata.game_id)
540 task->when = rcheevos_async_send_rich_presence(&rcheevos_locals,
541 request);
542 else
543 {
544 /* game changed; stop the recurring task - a new one will
545 * be scheduled for the next game */
546 task_set_finished(task, 1);
547 free(request);
548 }
549 break;
550
551 case CHEEVOS_ASYNC_AWARD_ACHIEVEMENT:
552 rcheevos_async_award_achievement(&rcheevos_locals, request);
553 task_set_finished(task, 1);
554 break;
555
556 case CHEEVOS_ASYNC_SUBMIT_LBOARD:
557 rcheevos_async_submit_lboard(&rcheevos_locals, request);
558 task_set_finished(task, 1);
559 break;
560 }
561 }
562
rcheevos_async_schedule(rcheevos_async_io_request * request,retro_time_t delay)563 static void rcheevos_async_schedule(
564 rcheevos_async_io_request* request, retro_time_t delay)
565 {
566 retro_task_t* task = task_init();
567 task->when = cpu_features_get_time_usec() + delay;
568 task->handler = rcheevos_async_task_handler;
569 task->user_data = request;
570 task->progress = -1;
571 task_queue_push(task);
572 }
573
rcheevos_async_task_callback(retro_task_t * task,void * task_data,void * user_data,const char * error)574 static void rcheevos_async_task_callback(
575 retro_task_t* task, void* task_data, void* user_data, const char* error)
576 {
577 rcheevos_async_io_request *request = (rcheevos_async_io_request*)user_data;
578 http_transfer_data_t *data = (http_transfer_data_t*)task_data;
579
580 if (!error)
581 {
582 char buffer[224] = "";
583 /* Server did not return HTTP headers */
584 if (!data)
585 snprintf(buffer, sizeof(buffer), "Server communication error");
586 else if (data->status != 200)
587 {
588 /* Server returned an error via status code.
589 * Check to see if it also returned a JSON error */
590 if (!data->data || rcheevos_get_json_error(data->data, buffer, sizeof(buffer)) != RC_OK)
591 snprintf(buffer, sizeof(buffer), "HTTP error code: %d",
592 data->status);
593 }
594 else if (!data->data || !data->len)
595 {
596 /* Server sent an empty response without an error status code */
597 snprintf(buffer, sizeof(buffer), "No response from server");
598 }
599 else
600 {
601 /* Server sent a message - assume it's JSON
602 * and check for a JSON error */
603 rcheevos_get_json_error(data->data, buffer, sizeof(buffer));
604 }
605
606 if (buffer[0])
607 {
608 char errbuf[256];
609 snprintf(errbuf, sizeof(errbuf), "%s %u: %s",
610 request->failure_message, request->id, buffer);
611 CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", errbuf);
612
613 switch (request->type)
614 {
615 case CHEEVOS_ASYNC_RICHPRESENCE:
616 /* Don't bother informing user when
617 * rich presence update fails */
618 break;
619
620 case CHEEVOS_ASYNC_AWARD_ACHIEVEMENT:
621 /* ignore already unlocked */
622 if (string_starts_with_size(buffer, "User already has ",
623 STRLEN_CONST("User already has ")))
624 break;
625 /* fallthrough to default */
626
627 default:
628 runloop_msg_queue_push(errbuf, 0, 5 * 60, false, NULL,
629 MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
630 break;
631 }
632 }
633 else
634 {
635 CHEEVOS_LOG(RCHEEVOS_TAG "%s %u\n", request->success_message, request->id);
636 }
637
638 free(request);
639 }
640 else
641 {
642 /* Double the wait between each attempt until we hit
643 * a maximum delay of two minutes.
644 * 250ms -> 500ms -> 1s -> 2s -> 4s -> 8s -> 16s -> 32s -> 64s -> 120s -> 120s... */
645 retro_time_t retry_delay =
646 (request->attempt_count > 8)
647 ? (120 * 1000 * 1000)
648 : ((250 * 1000) << request->attempt_count);
649
650 request->attempt_count++;
651 rcheevos_async_schedule(request, retry_delay);
652
653 CHEEVOS_ERR(RCHEEVOS_TAG "%s %u: %s\n", request->failure_message,
654 request->id, error);
655 }
656 }
657
rcheevos_activate_achievements(rcheevos_locals_t * locals,rcheevos_racheevo_t * cheevo,unsigned count,unsigned flags)658 static void rcheevos_activate_achievements(rcheevos_locals_t *locals,
659 rcheevos_racheevo_t* cheevo, unsigned count, unsigned flags)
660 {
661 int res;
662 unsigned i;
663 char buffer[256];
664 settings_t *settings = config_get_ptr();
665
666 for (i = 0; i < count; i++, cheevo++)
667 {
668 res = rc_runtime_activate_achievement(&locals->runtime, cheevo->id,
669 cheevo->memaddr, NULL, 0);
670
671 if (res < 0)
672 {
673 snprintf(buffer, sizeof(buffer),
674 "Could not activate achievement %d \"%s\": %s",
675 cheevo->id, cheevo->title, rc_error_str(res));
676
677 if (settings->bools.cheevos_verbose_enable)
678 runloop_msg_queue_push(buffer, 0, 4 * 60, false, NULL,
679 MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
680
681 CHEEVOS_ERR(RCHEEVOS_TAG "%s: mem %s\n", buffer, cheevo->memaddr);
682 CHEEVOS_FREE(cheevo->memaddr);
683 cheevo->memaddr = NULL;
684 continue;
685 }
686
687 cheevo->active =
688 RCHEEVOS_ACTIVE_SOFTCORE
689 | RCHEEVOS_ACTIVE_HARDCORE
690 | flags;
691 }
692 }
693
rcheevos_parse(rcheevos_locals_t * locals,const char * json)694 static int rcheevos_parse(rcheevos_locals_t *locals, const char* json)
695 {
696 char buffer[256];
697 unsigned j = 0;
698 unsigned count = 0;
699 settings_t *settings = NULL;
700 rcheevos_ralboard_t* lboard = NULL;
701 int res = rcheevos_get_patchdata(
702 json, &locals->patchdata);
703
704 if (res != 0)
705 {
706 char *ptr = NULL;
707 strcpy_literal(buffer, "Error retrieving achievement data: ");
708 ptr = buffer + strlen(buffer);
709
710 /* Extract the Error field from the JSON.
711 * If not found, remove the colon from the message. */
712 if (rcheevos_get_json_error(json, ptr,
713 sizeof(buffer) - (ptr - buffer)) == -1)
714 ptr[-2] = '\0';
715
716 runloop_msg_queue_push(buffer, 0, 5 * 60, false, NULL,
717 MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING);
718
719 RARCH_ERR(RCHEEVOS_TAG "%s", buffer);
720 return -1;
721 }
722
723 if ( locals->patchdata.core_count == 0
724 && locals->patchdata.unofficial_count == 0
725 && locals->patchdata.lboard_count == 0
726 && (!locals->patchdata.richpresence_script ||
727 !*locals->patchdata.richpresence_script))
728 {
729 rcheevos_free_patchdata(&locals->patchdata);
730 return 0;
731 }
732
733 settings = config_get_ptr();
734
735 if (!rcheevos_init_memory(locals))
736 {
737 /* some cores (like Mupen64-Plus) don't expose the
738 * memory until the first call to retro_run.
739 * in that case, there will be a total_size of
740 * memory reported by the core, but init will return
741 * false, as all of the pointers were null.
742 */
743
744 /* reset the memory count and we'll re-evaluate in rcheevos_test() */
745 if (locals->memory.total_size != 0)
746 locals->memory.count = 0;
747 else
748 {
749 CHEEVOS_ERR(RCHEEVOS_TAG "No memory exposed by core.\n");
750 rcheevos_locals.core_supports = false;
751
752 if (settings->bools.cheevos_verbose_enable)
753 runloop_msg_queue_push(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CANNOT_ACTIVATE_ACHIEVEMENTS_WITH_THIS_CORE),
754 0, 4 * 60, false, NULL,
755 MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING);
756
757 goto error;
758 }
759 }
760
761 /* Initialize. */
762 rcheevos_activate_achievements(locals, locals->patchdata.core,
763 locals->patchdata.core_count, 0);
764
765 if (settings->bools.cheevos_test_unofficial)
766 rcheevos_activate_achievements(locals, locals->patchdata.unofficial,
767 locals->patchdata.unofficial_count, RCHEEVOS_ACTIVE_UNOFFICIAL);
768
769 if (locals->hardcore_active && locals->leaderboards_enabled)
770 {
771 lboard = locals->patchdata.lboards;
772 count = locals->patchdata.lboard_count;
773
774 for (j = 0; j < count; j++, lboard++)
775 {
776 res = rc_runtime_activate_lboard(&locals->runtime, lboard->id,
777 lboard->mem, NULL, 0);
778
779 if (res < 0)
780 {
781 snprintf(buffer, sizeof(buffer),
782 "Could not activate leaderboard %d \"%s\": %s",
783 lboard->id, lboard->title, rc_error_str(res));
784
785 if (settings->bools.cheevos_verbose_enable)
786 runloop_msg_queue_push(buffer, 0, 4 * 60, false, NULL,
787 MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
788
789 CHEEVOS_ERR(RCHEEVOS_TAG "%s mem: %s\n", buffer, lboard->mem);
790 CHEEVOS_FREE(lboard->mem);
791 lboard->mem = NULL;
792 continue;
793 }
794 }
795 }
796
797 res = RC_MISSING_DISPLAY_STRING;
798 if ( locals->patchdata.richpresence_script
799 && *locals->patchdata.richpresence_script)
800 {
801 res = rc_runtime_activate_richpresence(&locals->runtime,
802 locals->patchdata.richpresence_script, NULL, 0);
803
804 if (res < 0)
805 {
806 snprintf(buffer, sizeof(buffer),
807 "Could not activate rich presence: %s",
808 rc_error_str(res));
809
810 if (settings->bools.cheevos_verbose_enable)
811 runloop_msg_queue_push(buffer, 0, 4 * 60, false, NULL,
812 MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
813
814 CHEEVOS_ERR(RCHEEVOS_TAG "%s\n", buffer);
815 }
816 }
817
818 /* schedule the first rich presence call in 30 seconds */
819 {
820 rcheevos_async_io_request* request = (rcheevos_async_io_request*)
821 calloc(1, sizeof(rcheevos_async_io_request));
822 request->id = locals->patchdata.game_id;
823 request->type = CHEEVOS_ASYNC_RICHPRESENCE;
824 rcheevos_async_schedule(request, CHEEVOS_PING_FREQUENCY / 4);
825 }
826
827 /* validate the memrefs */
828 if (rcheevos_locals.memory.count != 0)
829 rcheevos_validate_memrefs(&rcheevos_locals);
830
831 return 0;
832
833 error:
834 rcheevos_free_patchdata(&locals->patchdata);
835 rc_libretro_memory_destroy(&locals->memory);
836 return -1;
837 }
838
rcheevos_find_cheevo(unsigned id)839 static rcheevos_racheevo_t* rcheevos_find_cheevo(unsigned id)
840 {
841 unsigned i;
842 rcheevos_racheevo_t* cheevo;
843
844 cheevo = rcheevos_locals.patchdata.core;
845 for (i = 0; i < rcheevos_locals.patchdata.core_count; i++, cheevo++)
846 {
847 if (cheevo->id == id)
848 return cheevo;
849 }
850
851 cheevo = rcheevos_locals.patchdata.unofficial;
852 for (i = 0; i < rcheevos_locals.patchdata.unofficial_count; i++, cheevo++)
853 {
854 if (cheevo->id == id)
855 return cheevo;
856 }
857
858 return NULL;
859 }
860
rcheevos_award_achievement(rcheevos_locals_t * locals,rcheevos_racheevo_t * cheevo,bool widgets_ready)861 static void rcheevos_award_achievement(rcheevos_locals_t *locals,
862 rcheevos_racheevo_t* cheevo, bool widgets_ready)
863 {
864 char buffer[256] = "";
865
866 if (!cheevo)
867 return;
868
869 CHEEVOS_LOG(RCHEEVOS_TAG "Awarding achievement %u: %s (%s)\n",
870 cheevo->id, cheevo->title, cheevo->description);
871
872 /* Deactivates the cheevo. */
873 rc_runtime_deactivate_achievement(&locals->runtime, cheevo->id);
874
875 cheevo->active &= ~RCHEEVOS_ACTIVE_SOFTCORE;
876 if (locals->hardcore_active)
877 cheevo->active &= ~RCHEEVOS_ACTIVE_HARDCORE;
878
879 cheevo->unlock_time = cpu_features_get_time_usec();
880
881 /* Show the OSD message. */
882 {
883 #if defined(HAVE_GFX_WIDGETS)
884 if (widgets_ready)
885 gfx_widgets_push_achievement(cheevo->title, cheevo->badge);
886 else
887 #endif
888 {
889 snprintf(buffer, sizeof(buffer),
890 "Achievement Unlocked: %s", cheevo->title);
891 runloop_msg_queue_push(buffer, 0, 2 * 60, false, NULL,
892 MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
893 runloop_msg_queue_push(cheevo->description, 0, 3 * 60, false, NULL,
894 MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
895 }
896 }
897
898 /* Start the award task (unofficial achievement unlocks are not submitted). */
899 if (!(cheevo->active & RCHEEVOS_ACTIVE_UNOFFICIAL))
900 {
901 rcheevos_async_io_request *request = (rcheevos_async_io_request*)calloc(1, sizeof(rcheevos_async_io_request));
902 request->type = CHEEVOS_ASYNC_AWARD_ACHIEVEMENT;
903 request->id = cheevo->id;
904 request->hardcore = locals->hardcore_active ? 1 : 0;
905 request->success_message = "Awarded achievement";
906 request->failure_message = "Error awarding achievement";
907 rcheevos_get_user_agent(locals,
908 request->user_agent, sizeof(request->user_agent));
909 rcheevos_async_award_achievement(locals, request);
910 }
911
912 #ifdef HAVE_SCREENSHOTS
913 {
914 settings_t *settings = config_get_ptr();
915 /* Take a screenshot of the achievement. */
916 if (settings && settings->bools.cheevos_auto_screenshot)
917 {
918 size_t shotname_len = sizeof(char) * 8192;
919 char *shotname = (char*)malloc(shotname_len);
920
921 if (!shotname)
922 return;
923
924 snprintf(shotname, shotname_len, "%s/%s-cheevo-%u",
925 settings->paths.directory_screenshot,
926 path_basename(path_get(RARCH_PATH_BASENAME)),
927 cheevo->id);
928 shotname[shotname_len - 1] = '\0';
929
930 if (take_screenshot(settings->paths.directory_screenshot,
931 shotname, true,
932 video_driver_cached_frame_has_valid_framebuffer(),
933 false, true))
934 CHEEVOS_LOG(
935 RCHEEVOS_TAG "Captured screenshot for achievement %u\n",
936 cheevo->id);
937 else
938 CHEEVOS_LOG(
939 RCHEEVOS_TAG "Failed to capture screenshot for achievement %u\n",
940 cheevo->id);
941 free(shotname);
942 }
943 }
944 #endif
945 }
946
rcheevos_async_submit_lboard(rcheevos_locals_t * locals,rcheevos_async_io_request * request)947 static void rcheevos_async_submit_lboard(rcheevos_locals_t *locals,
948 rcheevos_async_io_request* request)
949 {
950 char buffer[256];
951 settings_t *settings = config_get_ptr();
952 int ret = rc_url_submit_lboard(buffer, sizeof(buffer),
953 settings->arrays.cheevos_username,
954 locals->token, request->id, request->value);
955
956 if (ret != 0)
957 {
958 CHEEVOS_ERR(RCHEEVOS_TAG "Buffer too small to create URL\n");
959 free(request);
960 return;
961 }
962
963 rcheevos_log_url("rc_url_submit_lboard", buffer);
964 task_push_http_transfer_with_user_agent(buffer, true, NULL,
965 request->user_agent, rcheevos_async_task_callback, request);
966 }
967
rcheevos_find_lboard(unsigned id)968 static rcheevos_ralboard_t* rcheevos_find_lboard(unsigned id)
969 {
970 rcheevos_ralboard_t* lboard = rcheevos_locals.patchdata.lboards;
971 unsigned i;
972
973 for (i = 0; i < rcheevos_locals.patchdata.lboard_count; ++i, ++lboard)
974 {
975 if (lboard->id == id)
976 return lboard;
977 }
978
979 return NULL;
980 }
981
rcheevos_lboard_submit(rcheevos_locals_t * locals,rcheevos_ralboard_t * lboard,int value,bool widgets_ready)982 static void rcheevos_lboard_submit(rcheevos_locals_t *locals,
983 rcheevos_ralboard_t* lboard, int value, bool widgets_ready)
984 {
985 char buffer[256];
986 char formatted_value[16];
987
988 /* Show the OSD message (regardless of notifications setting). */
989 rc_runtime_format_lboard_value(formatted_value, sizeof(formatted_value),
990 value, lboard->format);
991
992 CHEEVOS_LOG(RCHEEVOS_TAG "Submitting %s for leaderboard %u\n",
993 formatted_value, lboard->id);
994 snprintf(buffer, sizeof(buffer), "Submitted %s for %s",
995 formatted_value, lboard->title);
996 runloop_msg_queue_push(buffer, 0, 2 * 60, false, NULL,
997 MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
998
999 #if defined(HAVE_GFX_WIDGETS)
1000 /* Hide the tracker */
1001 if (widgets_ready)
1002 gfx_widgets_set_leaderboard_display(lboard->id, NULL);
1003 #endif
1004
1005 /* Start the submit task. */
1006 {
1007 rcheevos_async_io_request
1008 *request = (rcheevos_async_io_request*)
1009 calloc(1, sizeof(rcheevos_async_io_request));
1010
1011 request->type = CHEEVOS_ASYNC_SUBMIT_LBOARD;
1012 request->id = lboard->id;
1013 request->value = value;
1014 request->success_message = "Submitted leaderboard";
1015 request->failure_message = "Error submitting leaderboard";
1016 rcheevos_get_user_agent(locals,
1017 request->user_agent, sizeof(request->user_agent));
1018 rcheevos_async_submit_lboard(locals, request);
1019 }
1020 }
1021
rcheevos_lboard_canceled(rcheevos_ralboard_t * lboard,bool widgets_ready)1022 static void rcheevos_lboard_canceled(rcheevos_ralboard_t * lboard,
1023 bool widgets_ready)
1024 {
1025 char buffer[256];
1026 if (!lboard)
1027 return;
1028
1029 CHEEVOS_LOG(RCHEEVOS_TAG "Leaderboard %u canceled: %s\n",
1030 lboard->id, lboard->title);
1031
1032 #if defined(HAVE_GFX_WIDGETS)
1033 if (widgets_ready)
1034 gfx_widgets_set_leaderboard_display(lboard->id, NULL);
1035 #endif
1036
1037 if (rcheevos_locals.leaderboard_notifications)
1038 {
1039 snprintf(buffer, sizeof(buffer),
1040 "Leaderboard attempt failed: %s", lboard->title);
1041 runloop_msg_queue_push(buffer, 0, 2 * 60, false, NULL,
1042 MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
1043 }
1044 }
1045
rcheevos_lboard_started(rcheevos_ralboard_t * lboard,int value,bool widgets_ready)1046 static void rcheevos_lboard_started(rcheevos_ralboard_t * lboard, int value,
1047 bool widgets_ready)
1048 {
1049 char buffer[256];
1050 if (!lboard)
1051 return;
1052
1053 CHEEVOS_LOG(RCHEEVOS_TAG "Leaderboard %u started: %s\n",
1054 lboard->id, lboard->title);
1055
1056 #if defined(HAVE_GFX_WIDGETS)
1057 if (widgets_ready && rcheevos_locals.leaderboard_trackers)
1058 {
1059 rc_runtime_format_lboard_value(buffer, sizeof(buffer), value, lboard->format);
1060 gfx_widgets_set_leaderboard_display(lboard->id, buffer);
1061 }
1062 #endif
1063
1064 if (rcheevos_locals.leaderboard_notifications)
1065 {
1066 if (lboard->description && *lboard->description)
1067 snprintf(buffer, sizeof(buffer),
1068 "Leaderboard attempt started: %s - %s",
1069 lboard->title, lboard->description);
1070 else
1071 snprintf(buffer, sizeof(buffer),
1072 "Leaderboard attempt started: %s",
1073 lboard->title);
1074
1075 runloop_msg_queue_push(buffer, 0, 2 * 60, false, NULL,
1076 MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
1077 }
1078 }
1079
1080 #if defined(HAVE_GFX_WIDGETS)
rcheevos_lboard_updated(rcheevos_ralboard_t * lboard,int value,bool widgets_ready)1081 static void rcheevos_lboard_updated(rcheevos_ralboard_t* lboard, int value,
1082 bool widgets_ready)
1083 {
1084 if (!lboard)
1085 return;
1086
1087 if (widgets_ready && rcheevos_locals.leaderboard_trackers)
1088 {
1089 char buffer[32];
1090 rc_runtime_format_lboard_value(buffer, sizeof(buffer), value, lboard->format);
1091 gfx_widgets_set_leaderboard_display(lboard->id, buffer);
1092 }
1093 }
1094
rcheevos_challenge_started(rcheevos_racheevo_t * cheevo,int value,bool widgets_ready)1095 static void rcheevos_challenge_started(rcheevos_racheevo_t* cheevo, int value,
1096 bool widgets_ready)
1097 {
1098 settings_t* settings = config_get_ptr();
1099 if (cheevo && widgets_ready && settings->bools.cheevos_challenge_indicators)
1100 gfx_widgets_set_challenge_display(cheevo->id, cheevo->badge);
1101 }
1102
rcheevos_challenge_ended(rcheevos_racheevo_t * cheevo,int value,bool widgets_ready)1103 static void rcheevos_challenge_ended(rcheevos_racheevo_t* cheevo, int value,
1104 bool widgets_ready)
1105 {
1106 if (cheevo && widgets_ready)
1107 gfx_widgets_set_challenge_display(cheevo->id, NULL);
1108 }
1109
1110 #endif
1111
rcheevos_get_richpresence(char buffer[],int buffer_size)1112 int rcheevos_get_richpresence(char buffer[], int buffer_size)
1113 {
1114 int ret = rc_runtime_get_richpresence(&rcheevos_locals.runtime, buffer, buffer_size, &rcheevos_peek, NULL, NULL);
1115
1116 if (ret <= 0 && rcheevos_locals.patchdata.title)
1117 ret = snprintf(buffer, buffer_size, "Playing %s", rcheevos_locals.patchdata.title);
1118
1119 return ret;
1120 }
1121
rcheevos_reset_game(bool widgets_ready)1122 void rcheevos_reset_game(bool widgets_ready)
1123 {
1124 #if defined(HAVE_GFX_WIDGETS)
1125 /* Hide any visible trackers */
1126 if (widgets_ready)
1127 {
1128 rcheevos_ralboard_t* lboard = rcheevos_locals.patchdata.lboards;
1129 unsigned i;
1130
1131 for (i = 0; i < rcheevos_locals.patchdata.lboard_count; ++i, ++lboard)
1132 gfx_widgets_set_leaderboard_display(lboard->id, NULL);
1133 }
1134 #endif
1135
1136 rc_runtime_reset(&rcheevos_locals.runtime);
1137
1138 /* Some cores reallocate memory on reset,
1139 * make sure we update our pointers */
1140 if (rcheevos_locals.memory.total_size > 0)
1141 rcheevos_init_memory(&rcheevos_locals);
1142 }
1143
rcheevos_hardcore_active(void)1144 bool rcheevos_hardcore_active(void)
1145 {
1146 return rcheevos_locals.hardcore_active;
1147 }
1148
rcheevos_pause_hardcore(void)1149 void rcheevos_pause_hardcore(void)
1150 {
1151 if (rcheevos_locals.hardcore_active)
1152 rcheevos_toggle_hardcore_paused();
1153 }
1154
rcheevos_unload(void)1155 bool rcheevos_unload(void)
1156 {
1157 bool running = false;
1158 settings_t* settings = config_get_ptr();
1159
1160 CHEEVOS_LOCK(rcheevos_locals.task_lock);
1161 running = rcheevos_locals.task != NULL;
1162 CHEEVOS_UNLOCK(rcheevos_locals.task_lock);
1163
1164 if (running)
1165 {
1166 CHEEVOS_LOG(RCHEEVOS_TAG "Asked the load thread to terminate\n");
1167 task_queue_cancel_task(rcheevos_locals.task);
1168
1169 #ifdef HAVE_THREADS
1170 do
1171 {
1172 CHEEVOS_LOCK(rcheevos_locals.task_lock);
1173 running = rcheevos_locals.task != NULL;
1174 CHEEVOS_UNLOCK(rcheevos_locals.task_lock);
1175 } while(running);
1176 #endif
1177 }
1178
1179 if (rcheevos_locals.memory.count > 0)
1180 rc_libretro_memory_destroy(&rcheevos_locals.memory);
1181
1182 if (rcheevos_locals.loaded)
1183 {
1184 #ifdef HAVE_MENU
1185 rcheevos_menu_reset_badges();
1186
1187 if (rcheevos_locals.menuitems)
1188 {
1189 CHEEVOS_FREE(rcheevos_locals.menuitems);
1190 rcheevos_locals.menuitems = NULL;
1191 rcheevos_locals.menuitem_capacity = rcheevos_locals.menuitem_count = 0;
1192 }
1193 #endif
1194 rcheevos_free_patchdata(&rcheevos_locals.patchdata);
1195
1196 rcheevos_locals.loaded = false;
1197 rcheevos_locals.hardcore_active = false;
1198 }
1199
1200 #ifdef HAVE_THREADS
1201 rcheevos_locals.queued_command = CMD_EVENT_NONE;
1202 #endif
1203
1204 rc_runtime_destroy(&rcheevos_locals.runtime);
1205
1206 /* If the config-level token has been cleared,
1207 * we need to re-login on loading the next game */
1208 if (!settings->arrays.cheevos_token[0])
1209 rcheevos_locals.token[0] = '\0';
1210
1211 return true;
1212 }
1213
rcheevos_toggle_hardcore_achievements(rcheevos_locals_t * locals,rcheevos_racheevo_t * cheevo,unsigned count)1214 static void rcheevos_toggle_hardcore_achievements(rcheevos_locals_t *locals,
1215 rcheevos_racheevo_t* cheevo, unsigned count)
1216 {
1217 const unsigned active_mask =
1218 RCHEEVOS_ACTIVE_SOFTCORE | RCHEEVOS_ACTIVE_HARDCORE;
1219
1220 while (count--)
1221 {
1222 if (cheevo->memaddr && (cheevo->active & active_mask)
1223 == RCHEEVOS_ACTIVE_HARDCORE)
1224 {
1225 /* player has unlocked achievement in non-hardcore,
1226 * but has not unlocked in hardcore. Toggle state */
1227 if (locals->hardcore_active)
1228 {
1229 rc_runtime_activate_achievement(&locals->runtime, cheevo->id, cheevo->memaddr, NULL, 0);
1230 CHEEVOS_LOG(RCHEEVOS_TAG "Achievement %u activated: %s\n", cheevo->id, cheevo->title);
1231 }
1232 else
1233 {
1234 rc_runtime_deactivate_achievement(&locals->runtime, cheevo->id);
1235 CHEEVOS_LOG(RCHEEVOS_TAG "Achievement %u deactivated: %s\n", cheevo->id, cheevo->title);
1236 }
1237 }
1238
1239 ++cheevo;
1240 }
1241 }
1242
rcheevos_activate_leaderboards(rcheevos_locals_t * locals)1243 static void rcheevos_activate_leaderboards(rcheevos_locals_t* locals)
1244 {
1245 rcheevos_ralboard_t* lboard = locals->patchdata.lboards;
1246 unsigned i;
1247
1248 for (i = 0; i < locals->patchdata.lboard_count; ++i, ++lboard)
1249 {
1250 if (lboard->mem)
1251 rc_runtime_activate_lboard(&locals->runtime, lboard->id,
1252 lboard->mem, NULL, 0);
1253 }
1254 }
1255
rcheevos_deactivate_leaderboards(rcheevos_locals_t * locals)1256 static void rcheevos_deactivate_leaderboards(rcheevos_locals_t* locals)
1257 {
1258 rcheevos_ralboard_t* lboard = locals->patchdata.lboards;
1259 unsigned i;
1260
1261 for (i = 0; i < locals->patchdata.lboard_count; ++i, ++lboard)
1262 {
1263 if (lboard->mem)
1264 {
1265 rc_runtime_deactivate_lboard(&locals->runtime, lboard->id);
1266
1267 #if defined(HAVE_GFX_WIDGETS)
1268 /* Hide any visible trackers */
1269 gfx_widgets_set_leaderboard_display(lboard->id, NULL);
1270 #endif
1271 }
1272 }
1273 }
1274
rcheevos_leaderboards_enabled_changed(void)1275 void rcheevos_leaderboards_enabled_changed(void)
1276 {
1277 const settings_t* settings = config_get_ptr();
1278 const bool leaderboards_enabled = rcheevos_locals.leaderboards_enabled;
1279 const bool leaderboard_trackers = rcheevos_locals.leaderboard_trackers;
1280
1281 rcheevos_locals.leaderboards_enabled = rcheevos_locals.hardcore_active;
1282
1283 if (string_is_equal(settings->arrays.cheevos_leaderboards_enable, "true"))
1284 {
1285 rcheevos_locals.leaderboard_notifications = true;
1286 rcheevos_locals.leaderboard_trackers = true;
1287 }
1288 #if defined(HAVE_GFX_WIDGETS)
1289 else if (string_is_equal(
1290 settings->arrays.cheevos_leaderboards_enable, "trackers"))
1291 {
1292 rcheevos_locals.leaderboard_notifications = false;
1293 rcheevos_locals.leaderboard_trackers = true;
1294 }
1295 else if (string_is_equal(
1296 settings->arrays.cheevos_leaderboards_enable, "notifications"))
1297 {
1298 rcheevos_locals.leaderboard_notifications = true;
1299 rcheevos_locals.leaderboard_trackers = false;
1300 }
1301 #endif
1302 else
1303 {
1304 rcheevos_locals.leaderboards_enabled = false;
1305 rcheevos_locals.leaderboard_notifications = false;
1306 rcheevos_locals.leaderboard_trackers = false;
1307 }
1308
1309 if (rcheevos_locals.loaded)
1310 {
1311 if (leaderboards_enabled != rcheevos_locals.leaderboards_enabled)
1312 {
1313 if (rcheevos_locals.leaderboards_enabled)
1314 rcheevos_activate_leaderboards(&rcheevos_locals);
1315 else
1316 rcheevos_deactivate_leaderboards(&rcheevos_locals);
1317 }
1318
1319 #if defined(HAVE_GFX_WIDGETS)
1320 if (!rcheevos_locals.leaderboard_trackers && leaderboard_trackers)
1321 {
1322 /* Hide any visible trackers */
1323 unsigned i;
1324 rcheevos_ralboard_t* lboard = rcheevos_locals.patchdata.lboards;
1325
1326 for (i = 0; i < rcheevos_locals.patchdata.lboard_count; ++i, ++lboard)
1327 {
1328 if (lboard->mem)
1329 gfx_widgets_set_leaderboard_display(lboard->id, NULL);
1330 }
1331 }
1332 #endif
1333 }
1334 }
1335
rcheevos_toggle_hardcore_active(rcheevos_locals_t * locals)1336 static void rcheevos_toggle_hardcore_active(rcheevos_locals_t* locals)
1337 {
1338 settings_t* settings = config_get_ptr();
1339 bool rewind_enable = settings->bools.rewind_enable;
1340
1341 if (!locals->hardcore_active)
1342 {
1343 /* Activate hardcore */
1344 locals->hardcore_active = true;
1345
1346 /* If one or more invalid settings is enabled, abort*/
1347 rcheevos_validate_config_settings();
1348 if (!locals->hardcore_active)
1349 return;
1350
1351 #ifdef HAVE_CHEATS
1352 /* If one or more emulator managed cheats is active, abort */
1353 cheat_manager_apply_cheats();
1354 if (!locals->hardcore_active)
1355 return;
1356 #endif
1357
1358 if (locals->loaded)
1359 {
1360 const char* msg = msg_hash_to_str(MSG_CHEEVOS_HARDCORE_MODE_ENABLE);
1361 CHEEVOS_LOG("%s\n", msg);
1362 runloop_msg_queue_push(msg, 0, 3 * 60, true, NULL,
1363 MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
1364
1365 /* Reactivate leaderboards */
1366 if (locals->leaderboards_enabled)
1367 rcheevos_activate_leaderboards(locals);
1368
1369 /* reset the game */
1370 command_event(CMD_EVENT_RESET, NULL);
1371 }
1372
1373 /* deinit rewind */
1374 if (rewind_enable)
1375 {
1376 #ifdef HAVE_THREADS
1377 /* have to "schedule" this.
1378 * CMD_EVENT_REWIND_DEINIT should
1379 * only be called on the main thread */
1380 rcheevos_locals.queued_command = CMD_EVENT_REWIND_DEINIT;
1381 #else
1382 command_event(CMD_EVENT_REWIND_DEINIT, NULL);
1383 #endif
1384 }
1385 }
1386 else
1387 {
1388 /* pause hardcore */
1389 locals->hardcore_active = false;
1390
1391 if (locals->loaded)
1392 {
1393 CHEEVOS_LOG(RCHEEVOS_TAG "Hardcore paused\n");
1394
1395 /* deactivate leaderboards */
1396 rcheevos_deactivate_leaderboards(locals);
1397 }
1398
1399 /* re-init rewind */
1400 if (rewind_enable)
1401 {
1402 #ifdef HAVE_THREADS
1403 /* have to "schedule" this.
1404 * CMD_EVENT_REWIND_INIT should
1405 * only be called on the main thread */
1406 rcheevos_locals.queued_command = CMD_EVENT_REWIND_INIT;
1407 #else
1408 command_event(CMD_EVENT_REWIND_INIT, NULL);
1409 #endif
1410 }
1411 }
1412
1413 if (locals->loaded)
1414 {
1415 rcheevos_toggle_hardcore_achievements(locals,
1416 locals->patchdata.core, locals->patchdata.core_count);
1417 if (settings->bools.cheevos_test_unofficial)
1418 rcheevos_toggle_hardcore_achievements(locals,
1419 locals->patchdata.unofficial,
1420 locals->patchdata.unofficial_count);
1421 }
1422 }
1423
rcheevos_toggle_hardcore_paused(void)1424 void rcheevos_toggle_hardcore_paused(void)
1425 {
1426 settings_t* settings = config_get_ptr();
1427 /* if hardcore mode is not enabled, we can't toggle it */
1428 if (settings->bools.cheevos_hardcore_mode_enable)
1429 rcheevos_toggle_hardcore_active(&rcheevos_locals);
1430 }
1431
rcheevos_hardcore_enabled_changed(void)1432 void rcheevos_hardcore_enabled_changed(void)
1433 {
1434 const settings_t* settings = config_get_ptr();
1435 const bool enabled = settings
1436 && settings->bools.cheevos_enable
1437 && settings->bools.cheevos_hardcore_mode_enable;
1438
1439 if (enabled != rcheevos_locals.hardcore_active)
1440 {
1441 rcheevos_toggle_hardcore_active(&rcheevos_locals);
1442
1443 /* update leaderboard state flags */
1444 rcheevos_leaderboards_enabled_changed();
1445 }
1446 }
1447
rcheevos_validate_config_settings(void)1448 void rcheevos_validate_config_settings(void)
1449 {
1450 const rc_disallowed_setting_t* disallowed_settings;
1451 core_option_manager_t* coreopts = NULL;
1452 struct retro_system_info* system = runloop_get_libretro_system_info();
1453 int i;
1454
1455 if (!system->library_name || !rcheevos_locals.hardcore_active)
1456 return;
1457
1458 disallowed_settings = rc_libretro_get_disallowed_settings(system->library_name);
1459 if (disallowed_settings == NULL)
1460 return;
1461
1462 if (!rarch_ctl(RARCH_CTL_CORE_OPTIONS_LIST_GET, &coreopts))
1463 return;
1464
1465 for (i = 0; i < coreopts->size; i++)
1466 {
1467 const char* key = coreopts->opts[i].key;
1468 const char* val = core_option_manager_get_val(coreopts, i);
1469 if (!rc_libretro_is_setting_allowed(disallowed_settings, key, val))
1470 {
1471 char buffer[256];
1472 snprintf(buffer, sizeof(buffer), "Hardcore paused. Setting not allowed: %s=%s", key, val);
1473 CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", buffer);
1474 rcheevos_pause_hardcore();
1475
1476 runloop_msg_queue_push(buffer, 0, 4 * 60, false, NULL,
1477 MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING);
1478
1479 break;
1480 }
1481 }
1482 }
1483
rcheevos_runtime_event_handler(const rc_runtime_event_t * runtime_event)1484 static void rcheevos_runtime_event_handler(const rc_runtime_event_t* runtime_event)
1485 {
1486 #if defined(HAVE_GFX_WIDGETS)
1487 bool widgets_ready = gfx_widgets_ready();
1488 #else
1489 bool widgets_ready = false;
1490 #endif
1491
1492 switch (runtime_event->type)
1493 {
1494 #if defined(HAVE_GFX_WIDGETS)
1495 case RC_RUNTIME_EVENT_LBOARD_UPDATED:
1496 rcheevos_lboard_updated(rcheevos_find_lboard(runtime_event->id), runtime_event->value, widgets_ready);
1497 break;
1498
1499 case RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED:
1500 rcheevos_challenge_started(rcheevos_find_cheevo(runtime_event->id), runtime_event->value, widgets_ready);
1501 break;
1502
1503 case RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED:
1504 rcheevos_challenge_ended(rcheevos_find_cheevo(runtime_event->id), runtime_event->value, widgets_ready);
1505 break;
1506 #endif
1507
1508 case RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED:
1509 rcheevos_award_achievement(&rcheevos_locals, rcheevos_find_cheevo(runtime_event->id), widgets_ready);
1510 break;
1511
1512 case RC_RUNTIME_EVENT_LBOARD_STARTED:
1513 rcheevos_lboard_started(rcheevos_find_lboard(runtime_event->id), runtime_event->value, widgets_ready);
1514 break;
1515
1516 case RC_RUNTIME_EVENT_LBOARD_CANCELED:
1517 rcheevos_lboard_canceled(rcheevos_find_lboard(runtime_event->id),
1518 widgets_ready);
1519 break;
1520
1521 case RC_RUNTIME_EVENT_LBOARD_TRIGGERED:
1522 rcheevos_lboard_submit(&rcheevos_locals, rcheevos_find_lboard(runtime_event->id), runtime_event->value, widgets_ready);
1523 break;
1524
1525 case RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED:
1526 rcheevos_achievement_disabled(rcheevos_find_cheevo(runtime_event->id), runtime_event->value);
1527 break;
1528
1529 case RC_RUNTIME_EVENT_LBOARD_DISABLED:
1530 rcheevos_lboard_disabled(rcheevos_find_lboard(runtime_event->id), runtime_event->value);
1531 break;
1532
1533 default:
1534 break;
1535 }
1536 }
1537
rcheevos_runtime_address_validator(unsigned address)1538 static int rcheevos_runtime_address_validator(unsigned address)
1539 {
1540 return (rc_libretro_memory_find(&rcheevos_locals.memory, address) != NULL);
1541 }
1542
rcheevos_validate_memrefs(rcheevos_locals_t * locals)1543 static void rcheevos_validate_memrefs(rcheevos_locals_t* locals)
1544 {
1545 rc_runtime_validate_addresses(&locals->runtime,
1546 rcheevos_runtime_event_handler, rcheevos_runtime_address_validator);
1547 }
1548
1549 /*****************************************************************************
1550 Test all the achievements (call once per frame).
1551 *****************************************************************************/
rcheevos_test(void)1552 void rcheevos_test(void)
1553 {
1554 #ifdef HAVE_THREADS
1555 if (rcheevos_locals.queued_command != CMD_EVENT_NONE)
1556 {
1557 command_event(rcheevos_locals.queued_command, NULL);
1558 rcheevos_locals.queued_command = CMD_EVENT_NONE;
1559 }
1560 #endif
1561
1562 if (!rcheevos_locals.loaded)
1563 return;
1564
1565 if (rcheevos_locals.memory.count == 0)
1566 {
1567 /* we were unable to initialize memory earlier, try now */
1568 if (!rcheevos_init_memory(&rcheevos_locals))
1569 {
1570 const settings_t* settings = config_get_ptr();
1571 rcheevos_locals.core_supports = false;
1572
1573 CHEEVOS_ERR(RCHEEVOS_TAG "No memory exposed by core\n");
1574
1575 if (settings && settings->bools.cheevos_verbose_enable)
1576 {
1577 runloop_msg_queue_push(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CANNOT_ACTIVATE_ACHIEVEMENTS_WITH_THIS_CORE),
1578 0, 4 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING);
1579 }
1580
1581 rcheevos_unload();
1582 rcheevos_pause_hardcore();
1583 return;
1584 }
1585
1586 rcheevos_validate_memrefs(&rcheevos_locals);
1587 }
1588
1589 rc_runtime_do_frame(&rcheevos_locals.runtime, &rcheevos_runtime_event_handler, rcheevos_peek, NULL, 0);
1590 }
1591
rcheevos_get_serialize_size(void)1592 size_t rcheevos_get_serialize_size(void)
1593 {
1594 if (!rcheevos_locals.loaded)
1595 return 0;
1596 return rc_runtime_progress_size(&rcheevos_locals.runtime, NULL);
1597 }
1598
rcheevos_get_serialized_data(void * buffer)1599 bool rcheevos_get_serialized_data(void* buffer)
1600 {
1601 if (!rcheevos_locals.loaded)
1602 return false;
1603 return (rc_runtime_serialize_progress(buffer, &rcheevos_locals.runtime, NULL) == RC_OK);
1604 }
1605
rcheevos_set_serialized_data(void * buffer)1606 bool rcheevos_set_serialized_data(void* buffer)
1607 {
1608 if (rcheevos_locals.loaded)
1609 {
1610 if (buffer && rc_runtime_deserialize_progress(&rcheevos_locals.runtime, (const unsigned char*)buffer, NULL) == RC_OK)
1611 return true;
1612
1613 rc_runtime_reset(&rcheevos_locals.runtime);
1614 }
1615
1616 return false;
1617 }
1618
rcheevos_set_support_cheevos(bool state)1619 void rcheevos_set_support_cheevos(bool state)
1620 {
1621 rcheevos_locals.core_supports = state;
1622 }
1623
rcheevos_get_support_cheevos(void)1624 bool rcheevos_get_support_cheevos(void)
1625 {
1626 return rcheevos_locals.core_supports;
1627 }
1628
rcheevos_get_hash(void)1629 const char* rcheevos_get_hash(void)
1630 {
1631 return rcheevos_locals.hash;
1632 }
1633
rcheevos_unlock_cb(unsigned id,void * userdata)1634 static void rcheevos_unlock_cb(unsigned id, void* userdata)
1635 {
1636 rcheevos_racheevo_t* cheevo = rcheevos_find_cheevo(id);
1637 if (cheevo)
1638 {
1639 unsigned mode = *(unsigned*)userdata;
1640 #ifndef CHEEVOS_DONT_DEACTIVATE
1641 cheevo->active &= ~mode;
1642 #endif
1643
1644 if ((rcheevos_locals.hardcore_active && mode == RCHEEVOS_ACTIVE_HARDCORE) ||
1645 (!rcheevos_locals.hardcore_active && mode == RCHEEVOS_ACTIVE_SOFTCORE))
1646 {
1647 rc_runtime_deactivate_achievement(&rcheevos_locals.runtime, cheevo->id);
1648 CHEEVOS_LOG(RCHEEVOS_TAG "Achievement %u deactivated: %s\n", id, cheevo->title);
1649 }
1650 }
1651 }
1652
1653 #include "coro.h"
1654
1655 /* Uncomment the following two lines to debug rcheevos_iterate, this will
1656 * disable the coroutine yielding.
1657 *
1658 * The code is very easy to understand. It's meant to be like BASIC:
1659 * CORO_GOTO will jump execution to another label, CORO_GOSUB will
1660 * call another label, and CORO_RET will return from a CORO_GOSUB.
1661 *
1662 * This coroutine code is inspired in a very old pure C implementation
1663 * that runs everywhere:
1664 *
1665 * https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
1666 */
1667 /*#undef CORO_YIELD
1668 #define CORO_YIELD()*/
1669
1670 typedef struct
1671 {
1672 /* variables used in the co-routine */
1673 char badge_name[16];
1674 char url[256];
1675 char badge_basepath[PATH_MAX_LENGTH];
1676 char badge_fullpath[PATH_MAX_LENGTH];
1677 char hash[33];
1678 unsigned gameid;
1679 unsigned i;
1680 unsigned j;
1681 unsigned k;
1682 size_t len;
1683 retro_time_t t0;
1684 void *data;
1685 char *json;
1686 const char *path;
1687 rcheevos_racheevo_t *cheevo;
1688 const rcheevos_racheevo_t *cheevo_end;
1689 settings_t *settings;
1690 struct http_connection_t *conn;
1691 struct http_t *http;
1692 struct rc_hash_iterator iterator;
1693
1694 /* co-routine required fields */
1695 CORO_FIELDS
1696 } rcheevos_coro_t;
1697
1698 enum
1699 {
1700 /* Negative values because CORO_SUB generates positive values */
1701 RCHEEVOS_GET_GAMEID = -1,
1702 RCHEEVOS_GET_CHEEVOS = -2,
1703 RCHEEVOS_GET_BADGES = -3,
1704 RCHEEVOS_LOGIN = -4,
1705 RCHEEVOS_HTTP_GET = -5,
1706 RCHEEVOS_DEACTIVATE = -6,
1707 RCHEEVOS_PLAYING = -7,
1708 RCHEEVOS_DELAY = -8
1709 };
1710
rcheevos_iterate(rcheevos_coro_t * coro)1711 static int rcheevos_iterate(rcheevos_coro_t* coro)
1712 {
1713 char buffer[2048];
1714 bool ret;
1715 #ifdef CHEEVOS_TIME_HASH
1716 retro_time_t start;
1717 #endif
1718
1719 CORO_ENTER();
1720
1721 coro->settings = config_get_ptr();
1722
1723 /* Bail out if cheevos are disabled.
1724 * But set the above anyways,
1725 * command_read_ram needs it. */
1726 if (!coro->settings->bools.cheevos_enable)
1727 CORO_STOP();
1728
1729 /* iterate over the possible hashes for the file being loaded */
1730 rc_hash_initialize_iterator(&coro->iterator, coro->path, (uint8_t*)coro->data, coro->len);
1731 #ifdef CHEEVOS_TIME_HASH
1732 start = cpu_features_get_time_usec();
1733 #endif
1734 while (rc_hash_iterate(coro->hash, &coro->iterator))
1735 {
1736 #ifdef CHEEVOS_TIME_HASH
1737 CHEEVOS_LOG(RCHEEVOS_TAG "hash generated in %ums\n", (cpu_features_get_time_usec() - start) / 1000);
1738 #endif
1739 CORO_GOSUB(RCHEEVOS_GET_GAMEID);
1740 if (coro->gameid != 0)
1741 break;
1742
1743 #ifdef CHEEVOS_TIME_HASH
1744 start = cpu_features_get_time_usec();
1745 #endif
1746 }
1747 rc_hash_destroy_iterator(&coro->iterator);
1748
1749 /* if no match was found, bail */
1750 if (coro->gameid == 0)
1751 {
1752 CHEEVOS_LOG(RCHEEVOS_TAG "this game doesn't feature achievements\n");
1753 strlcpy(rcheevos_locals.hash, "N/A", sizeof(rcheevos_locals.hash));
1754 rcheevos_pause_hardcore();
1755 CORO_STOP();
1756 }
1757
1758 #ifdef CHEEVOS_JSON_OVERRIDE
1759 {
1760 size_t size = 0;
1761 FILE *file = fopen(CHEEVOS_JSON_OVERRIDE, "rb");
1762
1763 fseek(file, 0, SEEK_END);
1764 size = ftell(file);
1765 fseek(file, 0, SEEK_SET);
1766
1767 coro->json = (char*)malloc(size + 1);
1768 fread((void*)coro->json, 1, size, file);
1769
1770 fclose(file);
1771 coro->json[size] = 0;
1772 }
1773 #else
1774 CORO_GOSUB(RCHEEVOS_GET_CHEEVOS);
1775
1776 if (!coro->json)
1777 {
1778 runloop_msg_queue_push("Error loading achievements.", 0, 5 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
1779 CHEEVOS_ERR(RCHEEVOS_TAG "error loading achievements\n");
1780 CORO_STOP();
1781 }
1782 #endif
1783
1784 #ifdef CHEEVOS_SAVE_JSON
1785 {
1786 FILE *file = fopen(CHEEVOS_SAVE_JSON, "w");
1787 fwrite((void*)coro->json, 1, strlen(coro->json), file);
1788 fclose(file);
1789 }
1790 #endif
1791
1792 #if HAVE_REWIND
1793 if (!rcheevos_locals.hardcore_active)
1794 {
1795 /* deactivate rewind while we activate the achievements */
1796 if (coro->settings->bools.rewind_enable)
1797 {
1798 #ifdef HAVE_THREADS
1799 /* have to "schedule" this. CMD_EVENT_REWIND_DEINIT should only be called on the main thread */
1800 rcheevos_locals.queued_command = CMD_EVENT_REWIND_DEINIT;
1801
1802 /* wait for rewind to be disabled */
1803 while (rcheevos_locals.queued_command != CMD_EVENT_NONE)
1804 {
1805 CORO_YIELD();
1806 }
1807 #else
1808 command_event(CMD_EVENT_REWIND_DEINIT, NULL);
1809 #endif
1810 }
1811 }
1812 #endif
1813
1814 ret = rcheevos_parse(&rcheevos_locals, coro->json);
1815 CHEEVOS_FREE(coro->json);
1816
1817 if (ret == 0)
1818 {
1819 if ( rcheevos_locals.patchdata.core_count == 0
1820 && rcheevos_locals.patchdata.unofficial_count == 0
1821 && rcheevos_locals.patchdata.lboard_count == 0
1822 )
1823 {
1824 runloop_msg_queue_push(
1825 "This game has no achievements.",
1826 0, 5 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
1827
1828 if (rcheevos_locals.patchdata.richpresence_script &&
1829 *rcheevos_locals.patchdata.richpresence_script)
1830 {
1831 rcheevos_locals.loaded = true;
1832 }
1833 else
1834 {
1835 rcheevos_pause_hardcore();
1836 }
1837 }
1838 else
1839 {
1840 rcheevos_locals.loaded = true;
1841 }
1842 }
1843
1844 #if HAVE_REWIND
1845 if (!rcheevos_locals.hardcore_active)
1846 {
1847 /* re-enable rewind. if rcheevos_locals.loaded is true, additional space will be allocated
1848 * for the achievement state data */
1849 if (coro->settings->bools.rewind_enable)
1850 {
1851 #ifdef HAVE_THREADS
1852 /* have to "schedule" this. CMD_EVENT_REWIND_INIT should only be called on the main thread */
1853 rcheevos_locals.queued_command = CMD_EVENT_REWIND_INIT;
1854 #else
1855 command_event(CMD_EVENT_REWIND_INIT, NULL);
1856 #endif
1857 }
1858 }
1859 #endif
1860
1861 if (!rcheevos_locals.loaded)
1862 {
1863 /* parse failure or no achievements - nothing more to do */
1864 CORO_STOP();
1865 }
1866
1867 /*
1868 * Inputs: CHEEVOS_VAR_GAMEID
1869 * Outputs:
1870 */
1871 if (!coro->settings->bools.cheevos_start_active)
1872 CORO_GOSUB(RCHEEVOS_DEACTIVATE);
1873
1874 /*
1875 * Inputs: CHEEVOS_VAR_GAMEID
1876 * Outputs:
1877 */
1878 CORO_GOSUB(RCHEEVOS_PLAYING);
1879
1880 if (coro->settings->bools.cheevos_verbose_enable && rcheevos_locals.patchdata.core_count > 0)
1881 {
1882 char msg[256];
1883 int mode = RCHEEVOS_ACTIVE_SOFTCORE;
1884 const rcheevos_racheevo_t* cheevo = rcheevos_locals.patchdata.core;
1885 const rcheevos_racheevo_t* end = cheevo + rcheevos_locals.patchdata.core_count;
1886 int number_of_unlocked = rcheevos_locals.patchdata.core_count;
1887 int number_of_unsupported = 0;
1888
1889 if (rcheevos_locals.hardcore_active)
1890 mode = RCHEEVOS_ACTIVE_HARDCORE;
1891
1892 for (; cheevo < end; cheevo++)
1893 {
1894 if (!cheevo->memaddr)
1895 number_of_unsupported++;
1896 else if (cheevo->active & mode)
1897 number_of_unlocked--;
1898 }
1899
1900 if (!number_of_unsupported)
1901 {
1902 if (coro->settings->bools.cheevos_start_active)
1903 {
1904 snprintf(msg, sizeof(msg),
1905 "All %d achievements activated for this session.",
1906 rcheevos_locals.patchdata.core_count);
1907 CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", msg);
1908 }
1909 else
1910 {
1911 snprintf(msg, sizeof(msg),
1912 "You have %d of %d achievements unlocked.",
1913 number_of_unlocked, rcheevos_locals.patchdata.core_count);
1914 CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", &msg[9]);
1915 }
1916 }
1917 else
1918 {
1919 if (coro->settings->bools.cheevos_start_active)
1920 {
1921 snprintf(msg, sizeof(msg),
1922 "All %d achievements activated for this session (%d unsupported).",
1923 rcheevos_locals.patchdata.core_count,
1924 number_of_unsupported);
1925 CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", msg);
1926 }
1927 else
1928 {
1929 snprintf(msg, sizeof(msg),
1930 "You have %d of %d achievements unlocked (%d unsupported).",
1931 number_of_unlocked - number_of_unsupported,
1932 rcheevos_locals.patchdata.core_count,
1933 number_of_unsupported);
1934 CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", &msg[9]);
1935 }
1936 }
1937
1938 msg[sizeof(msg) - 1] = 0;
1939 runloop_msg_queue_push(msg, 0, 3 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
1940 }
1941
1942 CORO_GOSUB(RCHEEVOS_GET_BADGES);
1943 CORO_STOP();
1944
1945
1946 /**************************************************************************
1947 * Info Gets the achievements from Retro Achievements
1948 * Inputs coro->hash
1949 * Outputs coro->gameid
1950 *************************************************************************/
1951 CORO_SUB(RCHEEVOS_GET_GAMEID)
1952
1953 {
1954 int size;
1955
1956 CHEEVOS_LOG(RCHEEVOS_TAG "checking %s\n", coro->hash);
1957 memcpy(rcheevos_locals.hash, coro->hash, sizeof(coro->hash));
1958
1959 size = rc_url_get_gameid(coro->url, sizeof(coro->url), rcheevos_locals.hash);
1960 if (size < 0)
1961 {
1962 CHEEVOS_ERR(RCHEEVOS_TAG "buffer too small to create URL\n");
1963 CORO_RET();
1964 }
1965
1966 rcheevos_log_url("rc_url_get_gameid", coro->url);
1967 CORO_GOSUB(RCHEEVOS_HTTP_GET);
1968
1969 if (!coro->json)
1970 CORO_RET();
1971
1972 coro->gameid = chevos_get_gameid(coro->json);
1973
1974 CHEEVOS_FREE(coro->json);
1975 CHEEVOS_LOG(RCHEEVOS_TAG "got game id %u\n", coro->gameid);
1976 CORO_RET();
1977 }
1978
1979
1980 /**************************************************************************
1981 * Info Gets the achievements from Retro Achievements
1982 * Inputs CHEEVOS_VAR_GAMEID
1983 * Outputs CHEEVOS_VAR_JSON
1984 *************************************************************************/
1985 CORO_SUB(RCHEEVOS_GET_CHEEVOS)
1986 {
1987 int ret;
1988
1989 CORO_GOSUB(RCHEEVOS_LOGIN);
1990
1991 ret = rc_url_get_patch(coro->url, sizeof(coro->url), coro->settings->arrays.cheevos_username, rcheevos_locals.token, coro->gameid);
1992
1993 if (ret < 0)
1994 {
1995 CHEEVOS_ERR(RCHEEVOS_TAG "buffer too small to create URL\n");
1996 CORO_STOP();
1997 }
1998
1999 rcheevos_log_url("rc_url_get_patch", coro->url);
2000 CORO_GOSUB(RCHEEVOS_HTTP_GET);
2001
2002 if (!coro->json)
2003 {
2004 CHEEVOS_ERR(RCHEEVOS_TAG "error getting achievements for game id %u\n", coro->gameid);
2005 CORO_STOP();
2006 }
2007
2008 CHEEVOS_LOG(RCHEEVOS_TAG "got achievements for game id %u\n", coro->gameid);
2009 CORO_RET();
2010 }
2011
2012
2013 /**************************************************************************
2014 * Info Gets the achievements from Retro Achievements
2015 * Inputs CHEEVOS_VAR_GAMEID
2016 * Outputs CHEEVOS_VAR_JSON
2017 *************************************************************************/
2018 CORO_SUB(RCHEEVOS_GET_BADGES)
2019
2020 /* we always want badges if display widgets are enabled */
2021 #if !defined(HAVE_GFX_WIDGETS)
2022 {
2023 settings_t *settings = config_get_ptr();
2024 if (!(
2025 string_is_equal(settings->arrays.menu_driver, "xmb") ||
2026 string_is_equal(settings->arrays.menu_driver, "ozone")
2027 ) ||
2028 !settings->bools.cheevos_badges_enable)
2029 CORO_RET();
2030 }
2031 #endif
2032
2033 /* make sure the directory exists */
2034 coro->badge_fullpath[0] = '\0';
2035 fill_pathname_application_special(coro->badge_fullpath,
2036 sizeof(coro->badge_fullpath),
2037 APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES);
2038
2039 if (!path_is_directory(coro->badge_fullpath))
2040 path_mkdir(coro->badge_fullpath);
2041
2042 /* fetch the placeholder image */
2043 strlcpy(coro->badge_name, "00000" FILE_PATH_PNG_EXTENSION,
2044 sizeof(coro->badge_name));
2045 fill_pathname_join(coro->badge_fullpath, coro->badge_fullpath,
2046 coro->badge_name, sizeof(coro->badge_fullpath));
2047
2048 if (!path_is_valid(coro->badge_fullpath))
2049 {
2050 #ifdef CHEEVOS_LOG_BADGES
2051 CHEEVOS_LOG(RCHEEVOS_TAG "downloading badge %s\n",
2052 coro->badge_fullpath);
2053 #endif
2054 snprintf(coro->url, sizeof(coro->url),
2055 FILE_PATH_RETROACHIEVEMENTS_URL "/Badge/%s", coro->badge_name);
2056
2057 CORO_GOSUB(RCHEEVOS_HTTP_GET);
2058
2059 if (coro->json)
2060 {
2061 if (!filestream_write_file(coro->badge_fullpath, coro->json, coro->k))
2062 CHEEVOS_ERR(RCHEEVOS_TAG "Error writing badge %s\n", coro->badge_fullpath);
2063
2064 CHEEVOS_FREE(coro->json);
2065 coro->json = NULL;
2066 }
2067 }
2068
2069 /* fetch the game images */
2070 for (coro->i = 0; coro->i < 2; coro->i++)
2071 {
2072 if (coro->i == 0)
2073 {
2074 coro->cheevo = rcheevos_locals.patchdata.core;
2075 coro->cheevo_end = coro->cheevo + rcheevos_locals.patchdata.core_count;
2076 }
2077 else
2078 {
2079 coro->cheevo = rcheevos_locals.patchdata.unofficial;
2080 coro->cheevo_end = coro->cheevo + rcheevos_locals.patchdata.unofficial_count;
2081 }
2082
2083 for (; coro->cheevo < coro->cheevo_end; coro->cheevo++)
2084 {
2085 if (!coro->cheevo->badge || !coro->cheevo->badge[0])
2086 continue;
2087
2088 for (coro->j = 0 ; coro->j < 2; coro->j++)
2089 {
2090 CORO_YIELD();
2091
2092 if (coro->j == 0)
2093 snprintf(coro->badge_name,
2094 sizeof(coro->badge_name),
2095 "%s" FILE_PATH_PNG_EXTENSION,
2096 coro->cheevo->badge);
2097 else
2098 snprintf(coro->badge_name,
2099 sizeof(coro->badge_name),
2100 "%s_lock" FILE_PATH_PNG_EXTENSION,
2101 coro->cheevo->badge);
2102
2103 coro->badge_fullpath[0] = '\0';
2104 fill_pathname_application_special(coro->badge_fullpath,
2105 sizeof(coro->badge_fullpath),
2106 APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES);
2107
2108 fill_pathname_join(
2109 coro->badge_fullpath,
2110 coro->badge_fullpath,
2111 coro->badge_name,
2112 sizeof(coro->badge_fullpath));
2113
2114 if (!path_is_valid(coro->badge_fullpath))
2115 {
2116 #ifdef CHEEVOS_LOG_BADGES
2117 CHEEVOS_LOG(
2118 RCHEEVOS_TAG "downloading badge %s\n",
2119 coro->badge_fullpath);
2120 #endif
2121 snprintf(coro->url,
2122 sizeof(coro->url),
2123 FILE_PATH_RETROACHIEVEMENTS_URL "/Badge/%s",
2124 coro->badge_name);
2125
2126 CORO_GOSUB(RCHEEVOS_HTTP_GET);
2127
2128 if (coro->json)
2129 {
2130 if (!filestream_write_file(coro->badge_fullpath,
2131 coro->json, coro->k))
2132 CHEEVOS_ERR(RCHEEVOS_TAG "Error writing badge %s\n", coro->badge_fullpath);
2133
2134 CHEEVOS_FREE(coro->json);
2135 coro->json = NULL;
2136 }
2137 }
2138 }
2139 }
2140 }
2141
2142 CORO_RET();
2143
2144
2145 /**************************************************************************
2146 * Info Logs in the user at Retro Achievements
2147 *************************************************************************/
2148 CORO_SUB(RCHEEVOS_LOGIN)
2149 {
2150 int ret;
2151 char tok[256];
2152
2153 if (rcheevos_locals.token[0])
2154 CORO_RET();
2155
2156 if (string_is_empty(coro->settings->arrays.cheevos_username))
2157 {
2158 runloop_msg_queue_push(
2159 "Missing RetroAchievements account information.",
2160 0, 5 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
2161 runloop_msg_queue_push(
2162 "Please fill in your account information in Settings.",
2163 0, 5 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
2164 CHEEVOS_ERR(RCHEEVOS_TAG "login info not informed\n");
2165 CORO_STOP();
2166 }
2167
2168 if (string_is_empty(coro->settings->arrays.cheevos_token))
2169 {
2170 ret = rc_url_login_with_password(coro->url, sizeof(coro->url),
2171 coro->settings->arrays.cheevos_username,
2172 coro->settings->arrays.cheevos_password);
2173
2174 if (ret == RC_OK)
2175 {
2176 CHEEVOS_LOG(RCHEEVOS_TAG "attempting to login %s (with password)\n", coro->settings->arrays.cheevos_username);
2177 rcheevos_log_url("rc_url_login_with_password", coro->url);
2178 }
2179 }
2180 else
2181 {
2182 ret = rc_url_login_with_token(coro->url, sizeof(coro->url),
2183 coro->settings->arrays.cheevos_username,
2184 coro->settings->arrays.cheevos_token);
2185
2186 if (ret == RC_OK)
2187 {
2188 CHEEVOS_LOG(RCHEEVOS_TAG "attempting to login %s (with token)\n", coro->settings->arrays.cheevos_username);
2189 rcheevos_log_url("rc_url_login_with_token", coro->url);
2190 }
2191 }
2192
2193 if (ret < 0)
2194 {
2195 CHEEVOS_ERR(RCHEEVOS_TAG "buffer too small to create URL\n");
2196 CORO_STOP();
2197 }
2198
2199 CORO_GOSUB(RCHEEVOS_HTTP_GET);
2200
2201 if (!coro->json)
2202 {
2203 runloop_msg_queue_push("RetroAchievements: Error contacting server.", 0, 5 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
2204 CHEEVOS_ERR(RCHEEVOS_TAG "error getting user token\n");
2205
2206 CORO_STOP();
2207 }
2208
2209 ret = rcheevos_get_token(coro->json, tok, sizeof(tok));
2210
2211 if (ret != 0)
2212 {
2213 char msg[512];
2214 snprintf(msg, sizeof(msg),
2215 "RetroAchievements: %s",
2216 tok);
2217 runloop_msg_queue_push(msg, 0, 5 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
2218 *coro->settings->arrays.cheevos_token = 0;
2219 CHEEVOS_ERR(RCHEEVOS_TAG "login error: %s\n", tok);
2220
2221 CHEEVOS_FREE(coro->json);
2222 CORO_STOP();
2223 }
2224
2225 CHEEVOS_FREE(coro->json);
2226
2227 if (coro->settings->bools.cheevos_verbose_enable)
2228 {
2229 char msg[256];
2230 snprintf(msg, sizeof(msg),
2231 "RetroAchievements: Logged in as \"%s\".",
2232 coro->settings->arrays.cheevos_username);
2233 msg[sizeof(msg) - 1] = 0;
2234 runloop_msg_queue_push(msg, 0, 2 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
2235 }
2236
2237 CHEEVOS_LOG(RCHEEVOS_TAG "logged in successfully\n");
2238 strlcpy(rcheevos_locals.token, tok,
2239 sizeof(rcheevos_locals.token));
2240
2241 /* Save token to config and clear pass on success */
2242 strlcpy(coro->settings->arrays.cheevos_token, tok,
2243 sizeof(coro->settings->arrays.cheevos_token));
2244
2245 *coro->settings->arrays.cheevos_password = 0;
2246 CORO_RET();
2247 }
2248
2249
2250 /**************************************************************************
2251 * Info Pauses execution for five seconds
2252 *************************************************************************/
2253 CORO_SUB(RCHEEVOS_DELAY)
2254
2255 {
2256 retro_time_t t1;
2257 coro->t0 = cpu_features_get_time_usec();
2258
2259 do
2260 {
2261 CORO_YIELD();
2262 t1 = cpu_features_get_time_usec();
2263 } while((t1 - coro->t0) < 3000000);
2264 }
2265
2266 CORO_RET();
2267
2268
2269 /**************************************************************************
2270 * Info Makes a HTTP GET request
2271 * Inputs CHEEVOS_VAR_URL
2272 * Outputs CHEEVOS_VAR_JSON
2273 *************************************************************************/
2274 CORO_SUB(RCHEEVOS_HTTP_GET)
2275
2276 for (coro->k = 0; coro->k < 5; coro->k++)
2277 {
2278 if (coro->k != 0)
2279 CHEEVOS_LOG(RCHEEVOS_TAG "Retrying HTTP request: %u of 5\n", coro->k + 1);
2280
2281 coro->json = NULL;
2282 coro->conn = net_http_connection_new(
2283 coro->url, "GET", NULL);
2284
2285 if (!coro->conn)
2286 {
2287 CORO_GOSUB(RCHEEVOS_DELAY);
2288 continue;
2289 }
2290
2291 /* Don't bother with timeouts here, it's just a string scan. */
2292 while (!net_http_connection_iterate(coro->conn)) {}
2293
2294 /* Error finishing the connection descriptor. */
2295 if (!net_http_connection_done(coro->conn))
2296 {
2297 net_http_connection_free(coro->conn);
2298 continue;
2299 }
2300
2301 rcheevos_get_user_agent(&rcheevos_locals,
2302 buffer, sizeof(buffer));
2303 net_http_connection_set_user_agent(coro->conn, buffer);
2304
2305 coro->http = net_http_new(coro->conn);
2306
2307 /* Error connecting to the endpoint. */
2308 if (!coro->http)
2309 {
2310 net_http_connection_free(coro->conn);
2311 CORO_GOSUB(RCHEEVOS_DELAY);
2312 continue;
2313 }
2314
2315 while (!net_http_update(coro->http, NULL, NULL))
2316 CORO_YIELD();
2317
2318 {
2319 size_t length;
2320 uint8_t *data = net_http_data(coro->http,
2321 &length, false);
2322
2323 if (data)
2324 {
2325 coro->json = (char*)malloc(length + 1);
2326
2327 if (coro->json)
2328 {
2329 memcpy((void*)coro->json, (void*)data, length);
2330 CHEEVOS_FREE(data);
2331 coro->json[length] = 0;
2332 }
2333
2334 coro->k = (unsigned)length;
2335 net_http_delete(coro->http);
2336 net_http_connection_free(coro->conn);
2337 CORO_RET();
2338 }
2339 }
2340
2341 net_http_delete(coro->http);
2342 net_http_connection_free(coro->conn);
2343 }
2344
2345 CHEEVOS_LOG(RCHEEVOS_TAG "Couldn't connect to server after 5 tries\n");
2346 CORO_RET();
2347
2348
2349 /**************************************************************************
2350 * Info Deactivates the achievements already awarded
2351 * Inputs CHEEVOS_VAR_GAMEID
2352 * Outputs
2353 *************************************************************************/
2354 CORO_SUB(RCHEEVOS_DEACTIVATE)
2355
2356 CORO_GOSUB(RCHEEVOS_LOGIN);
2357 {
2358 int ret;
2359 unsigned mode;
2360
2361 /* Two calls - one for softcore and one for hardcore */
2362 for (coro->i = 0; coro->i < 2; coro->i++)
2363 {
2364 ret = rc_url_get_unlock_list(coro->url, sizeof(coro->url),
2365 coro->settings->arrays.cheevos_username,
2366 rcheevos_locals.token, coro->gameid, coro->i);
2367
2368 if (ret < 0)
2369 {
2370 CHEEVOS_ERR(RCHEEVOS_TAG "buffer too small to create URL\n");
2371 CORO_STOP();
2372 }
2373
2374 rcheevos_log_url("rc_url_get_unlock_list", coro->url);
2375 CORO_GOSUB(RCHEEVOS_HTTP_GET);
2376
2377 if (coro->json)
2378 {
2379 mode = coro->i == 0 ? RCHEEVOS_ACTIVE_SOFTCORE : RCHEEVOS_ACTIVE_HARDCORE;
2380 rcheevos_deactivate_unlocks(coro->json, rcheevos_unlock_cb, &mode);
2381 CHEEVOS_FREE(coro->json);
2382 }
2383 else
2384 CHEEVOS_ERR(RCHEEVOS_TAG "error retrieving list of unlocked achievements in softcore mode\n");
2385 }
2386 }
2387
2388 CORO_RET();
2389
2390
2391 /**************************************************************************
2392 * Info Posts the "playing" activity to Retro Achievements
2393 * Inputs CHEEVOS_VAR_GAMEID
2394 * Outputs
2395 *************************************************************************/
2396 CORO_SUB(RCHEEVOS_PLAYING)
2397
2398 {
2399 int ret = rc_url_post_playing(coro->url, sizeof(coro->url),
2400 coro->settings->arrays.cheevos_username,
2401 rcheevos_locals.token, coro->gameid);
2402
2403 if (ret < 0)
2404 {
2405 CHEEVOS_ERR(RCHEEVOS_TAG "buffer too small to create URL\n");
2406 CORO_STOP();
2407 }
2408 }
2409
2410 rcheevos_log_url("rc_url_post_playing", coro->url);
2411
2412 CORO_GOSUB(RCHEEVOS_HTTP_GET);
2413
2414 if (coro->json)
2415 {
2416 CHEEVOS_LOG(RCHEEVOS_TAG "Posted playing activity\n");
2417 CHEEVOS_FREE(coro->json);
2418 }
2419 else
2420 CHEEVOS_ERR(RCHEEVOS_TAG "error posting playing activity\n");
2421
2422 CORO_RET();
2423
2424 CORO_LEAVE();
2425 }
2426
rcheevos_task_handler(retro_task_t * task)2427 static void rcheevos_task_handler(retro_task_t *task)
2428 {
2429 rcheevos_coro_t *coro = (rcheevos_coro_t*)task->state;
2430
2431 if (!coro)
2432 return;
2433
2434 if (!rcheevos_iterate(coro) || task_get_cancelled(task))
2435 {
2436 task_set_finished(task, true);
2437
2438 CHEEVOS_LOCK(rcheevos_locals.task_lock);
2439 rcheevos_locals.task = NULL;
2440 CHEEVOS_UNLOCK(rcheevos_locals.task_lock);
2441
2442 if (task_get_cancelled(task))
2443 {
2444 CHEEVOS_LOG(RCHEEVOS_TAG "Load task cancelled\n");
2445 }
2446 else
2447 {
2448 CHEEVOS_LOG(RCHEEVOS_TAG "Load task finished\n");
2449 }
2450
2451 CHEEVOS_FREE(coro->data);
2452 CHEEVOS_FREE(coro->path);
2453 CHEEVOS_FREE(coro);
2454 }
2455 }
2456
2457 /* hooks for rc_hash library */
2458
rc_hash_handle_file_open(const char * path)2459 static void* rc_hash_handle_file_open(const char* path)
2460 {
2461 return intfstream_open_file(path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
2462 }
2463
rc_hash_handle_file_seek(void * file_handle,int64_t offset,int origin)2464 static void rc_hash_handle_file_seek(void* file_handle, int64_t offset, int origin)
2465 {
2466 intfstream_seek((intfstream_t*)file_handle, offset, origin);
2467 }
2468
rc_hash_handle_file_tell(void * file_handle)2469 static int64_t rc_hash_handle_file_tell(void* file_handle)
2470 {
2471 return intfstream_tell((intfstream_t*)file_handle);
2472 }
2473
rc_hash_handle_file_read(void * file_handle,void * buffer,size_t requested_bytes)2474 static size_t rc_hash_handle_file_read(void* file_handle, void* buffer, size_t requested_bytes)
2475 {
2476 return intfstream_read((intfstream_t*)file_handle, buffer, requested_bytes);
2477 }
2478
rc_hash_handle_file_close(void * file_handle)2479 static void rc_hash_handle_file_close(void* file_handle)
2480 {
2481 intfstream_close((intfstream_t*)file_handle);
2482 CHEEVOS_FREE(file_handle);
2483 }
2484
rc_hash_handle_cd_open_track(const char * path,uint32_t track)2485 static void* rc_hash_handle_cd_open_track(const char* path, uint32_t track)
2486 {
2487 cdfs_track_t* cdfs_track;
2488
2489 switch (track)
2490 {
2491 case RC_HASH_CDTRACK_FIRST_DATA:
2492 cdfs_track = cdfs_open_data_track(path);
2493 break;
2494
2495 case RC_HASH_CDTRACK_LAST:
2496 #ifdef HAVE_CHD
2497 if (string_is_equal_noncase(path_get_extension(path), "chd"))
2498 {
2499 cdfs_track = cdfs_open_track(path, CHDSTREAM_TRACK_LAST);
2500 break;
2501 }
2502 #endif
2503 CHEEVOS_LOG(RCHEEVOS_TAG "Last track only supported for CHD\n");
2504 cdfs_track = NULL;
2505 break;
2506
2507 case RC_HASH_CDTRACK_LARGEST:
2508 #ifdef HAVE_CHD
2509 if (string_is_equal_noncase(path_get_extension(path), "chd"))
2510 {
2511 cdfs_track = cdfs_open_track(path, CHDSTREAM_TRACK_PRIMARY);
2512 break;
2513 }
2514 #endif
2515 CHEEVOS_LOG(RCHEEVOS_TAG "Largest track only supported for CHD, using first data track\n");
2516 cdfs_track = cdfs_open_data_track(path);
2517 break;
2518
2519 default:
2520 cdfs_track = cdfs_open_track(path, track);
2521 break;
2522 }
2523
2524 if (cdfs_track)
2525 {
2526 cdfs_file_t* file = (cdfs_file_t*)malloc(sizeof(cdfs_file_t));
2527 if (cdfs_open_file(file, cdfs_track, NULL))
2528 return file; /* ASSERT: file owns cdfs_track now */
2529
2530 CHEEVOS_FREE(file);
2531 cdfs_close_track(cdfs_track); /* ASSERT: this free()s cdfs_track */
2532 }
2533
2534 return NULL;
2535 }
2536
rc_hash_handle_cd_read_sector(void * track_handle,uint32_t sector,void * buffer,size_t requested_bytes)2537 static size_t rc_hash_handle_cd_read_sector(void* track_handle, uint32_t sector, void* buffer, size_t requested_bytes)
2538 {
2539 cdfs_file_t* file = (cdfs_file_t*)track_handle;
2540
2541 cdfs_seek_sector(file, sector);
2542 return cdfs_read_file(file, buffer, requested_bytes);
2543 }
2544
rc_hash_handle_cd_close_track(void * track_handle)2545 static void rc_hash_handle_cd_close_track(void* track_handle)
2546 {
2547 cdfs_file_t* file = (cdfs_file_t*)track_handle;
2548 if (file)
2549 {
2550 cdfs_close_track(file->track);
2551 cdfs_close_file(file); /* ASSERT: this does not free() file */
2552 CHEEVOS_FREE(file);
2553 }
2554 }
2555
2556 /* end hooks */
2557
rcheevos_load(const void * data)2558 bool rcheevos_load(const void *data)
2559 {
2560 retro_task_t *task = NULL;
2561 const struct retro_game_info *info = NULL;
2562 rcheevos_coro_t *coro = NULL;
2563 settings_t *settings = config_get_ptr();
2564 bool cheevos_enable = settings && settings->bools.cheevos_enable;
2565 struct rc_hash_filereader filereader;
2566 struct rc_hash_cdreader cdreader;
2567
2568 rcheevos_locals.loaded = false;
2569 #ifdef HAVE_THREADS
2570 rcheevos_locals.queued_command = CMD_EVENT_NONE;
2571 #endif
2572 rc_runtime_init(&rcheevos_locals.runtime);
2573
2574 if (!cheevos_enable || !rcheevos_locals.core_supports || !data)
2575 {
2576 rcheevos_pause_hardcore();
2577 return false;
2578 }
2579
2580 /* reset hardcore mode and leaderboard settings based on configs */
2581 rcheevos_hardcore_enabled_changed();
2582 rcheevos_validate_config_settings();
2583 rcheevos_leaderboards_enabled_changed();
2584
2585 coro = (rcheevos_coro_t*)calloc(1, sizeof(*coro));
2586
2587 if (!coro)
2588 return false;
2589
2590 /* provide hooks for reading files */
2591 memset(&filereader, 0, sizeof(filereader));
2592 filereader.open = rc_hash_handle_file_open;
2593 filereader.seek = rc_hash_handle_file_seek;
2594 filereader.tell = rc_hash_handle_file_tell;
2595 filereader.read = rc_hash_handle_file_read;
2596 filereader.close = rc_hash_handle_file_close;
2597 rc_hash_init_custom_filereader(&filereader);
2598
2599 memset(&cdreader, 0, sizeof(cdreader));
2600 cdreader.open_track = rc_hash_handle_cd_open_track;
2601 cdreader.read_sector = rc_hash_handle_cd_read_sector;
2602 cdreader.close_track = rc_hash_handle_cd_close_track;
2603 rc_hash_init_custom_cdreader(&cdreader);
2604
2605 rc_hash_init_error_message_callback(rcheevos_handle_log_message);
2606
2607 #ifndef DEBUG /* in DEBUG mode, always initialize the verbose message handler */
2608 if (settings->bools.cheevos_verbose_enable)
2609 #endif
2610 {
2611 rc_hash_init_verbose_message_callback(rcheevos_handle_log_message);
2612 }
2613
2614 task = task_init();
2615 if (!task)
2616 {
2617 CHEEVOS_FREE(coro);
2618 return false;
2619 }
2620
2621 CORO_SETUP();
2622
2623 info = (const struct retro_game_info*)data;
2624 coro->path = strdup(info->path);
2625
2626 if (info->data)
2627 {
2628 coro->len = info->size;
2629
2630 /* size limit */
2631 if (coro->len > CHEEVOS_MB(64))
2632 coro->len = CHEEVOS_MB(64);
2633
2634 coro->data = malloc(coro->len);
2635
2636 if (!coro->data)
2637 {
2638 CHEEVOS_FREE(task);
2639 CHEEVOS_FREE(coro);
2640 return false;
2641 }
2642
2643 memcpy(coro->data, info->data, coro->len);
2644 }
2645 else
2646 {
2647 coro->data = NULL;
2648 }
2649
2650 task->handler = rcheevos_task_handler;
2651 task->state = (void*)coro;
2652 task->mute = true;
2653 task->callback = NULL;
2654 task->user_data = NULL;
2655 task->progress = 0;
2656 task->title = NULL;
2657
2658 #ifdef HAVE_THREADS
2659 if (!rcheevos_locals.task_lock)
2660 rcheevos_locals.task_lock = slock_new();
2661 #endif
2662
2663 CHEEVOS_LOCK(rcheevos_locals.task_lock);
2664 rcheevos_locals.task = task;
2665 CHEEVOS_UNLOCK(rcheevos_locals.task_lock);
2666
2667 task_queue_push(task);
2668 return true;
2669 }
2670