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