1 /*  RetroArch - A frontend for libretro.
2  *  Copyright (C) 2010-2014 - Hans-Kristian Arntzen
3  *  Copyright (C) 2011-2017 - Daniel De Matteis
4  *  Copyright (C) 2016-2019 - Brad Parker
5  *
6  *  RetroArch is free software: you can redistribute it and/or modify it under the terms
7  *  of the GNU General Public License as published by the Free Software Found-
8  *  ation, either version 3 of the License, or (at your option) any later version.
9  *
10  *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
11  *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12  *  PURPOSE.  See the GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License along with RetroArch.
15  *  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <retro_assert.h>
19 #include <compat/strl.h>
20 #include <string/stdstring.h>
21 #include <file/config_file.h>
22 #include <file/file_path.h>
23 #include <streams/file_stream.h>
24 #include <streams/interface_stream.h>
25 #include <formats/rjson.h>
26 #include <lists/dir_list.h>
27 #include <file/archive_file.h>
28 
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32 
33 #include "retroarch.h"
34 #include "verbosity.h"
35 
36 #include "core_info.h"
37 #include "file_path_special.h"
38 
39 #if defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
40 #include "uwp/uwp_func.h"
41 #endif
42 
43 #if defined(ANDROID)
44 #include "play_feature_delivery/play_feature_delivery.h"
45 #endif
46 
47 /*************************/
48 /* Core Info Cache START */
49 /*************************/
50 
51 #define CORE_INFO_CACHE_DEFAULT_CAPACITY 8
52 
53 /* TODO/FIXME: Apparently rzip compression is an issue on UWP */
54 #if defined(HAVE_ZLIB) && !(defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP)
55 #define CORE_INFO_CACHE_COMPRESS
56 #endif
57 
58 typedef struct
59 {
60    core_info_t *items;
61    size_t length;
62    size_t capacity;
63    bool refresh;
64 } core_info_cache_list_t;
65 
66 typedef struct
67 {
68    core_info_t *core_info;
69    core_info_cache_list_t *core_info_cache_list;
70    char **current_string_val;
71    struct string_list **current_string_list_val;
72    uint32_t *current_entry_uint_val;
73    bool *current_entry_bool_val;
74    unsigned array_depth;
75    unsigned object_depth;
76    bool to_core_file_id;
77    bool to_firmware;
78 } CCJSONContext;
79 
80 /* Forward declarations */
81 static void core_info_free(core_info_t* info);
82 static uint32_t core_info_hash_string(const char *str);
83 static core_info_cache_list_t *core_info_cache_list_new(void);
84 static void core_info_cache_add(core_info_cache_list_t *list, core_info_t *info,
85       bool transfer);
86 
87 /* JSON Handlers START */
88 
CCJSONObjectMemberHandler(void * context,const char * pValue,size_t length)89 static bool CCJSONObjectMemberHandler(void *context, const char *pValue, size_t length)
90 {
91    CCJSONContext *pCtx = (CCJSONContext *)context;
92 
93    if ((pCtx->object_depth == 2) && (pCtx->array_depth == 1) && length)
94    {
95       pCtx->current_string_val      = NULL;
96       pCtx->current_string_list_val = NULL;
97       pCtx->current_entry_uint_val  = NULL;
98       pCtx->current_entry_bool_val  = NULL;
99       pCtx->to_core_file_id         = false;
100       pCtx->to_firmware             = false;
101 
102       switch (pValue[0])
103       {
104          case 'a':
105             if (string_is_equal(pValue,      "authors"))
106             {
107                pCtx->current_string_val      = &pCtx->core_info->authors;
108                pCtx->current_string_list_val = &pCtx->core_info->authors_list;
109             }
110             break;
111          case 'c':
112             if (string_is_equal(pValue,      "categories"))
113             {
114                pCtx->current_string_val      = &pCtx->core_info->categories;
115                pCtx->current_string_list_val = &pCtx->core_info->categories_list;
116             }
117             else if (string_is_equal(pValue, "core_name"))
118                pCtx->current_string_val      = &pCtx->core_info->core_name;
119             else if (string_is_equal(pValue, "core_file_id"))
120                pCtx->to_core_file_id         = true;
121             break;
122          case 'd':
123             if (string_is_equal(pValue,      "display_name"))
124                pCtx->current_string_val      = &pCtx->core_info->display_name;
125             else if (string_is_equal(pValue, "display_version"))
126                pCtx->current_string_val      = &pCtx->core_info->display_version;
127             else if (string_is_equal(pValue, "databases"))
128             {
129                pCtx->current_string_val      = &pCtx->core_info->databases;
130                pCtx->current_string_list_val = &pCtx->core_info->databases_list;
131             }
132             else if (string_is_equal(pValue, "description"))
133                pCtx->current_string_val      = &pCtx->core_info->description;
134             else if (string_is_equal(pValue, "database_match_archive_member"))
135                pCtx->current_entry_bool_val  = &pCtx->core_info->database_match_archive_member;
136             break;
137          case 'f':
138             if (string_is_equal(pValue,      "firmware"))
139                pCtx->to_firmware             = true;
140             break;
141          case 'h':
142             if (string_is_equal(pValue,      "has_info"))
143                pCtx->current_entry_bool_val  = &pCtx->core_info->has_info;
144             break;
145          case 'l':
146             if (string_is_equal(pValue,      "licenses"))
147             {
148                pCtx->current_string_val      = &pCtx->core_info->licenses;
149                pCtx->current_string_list_val = &pCtx->core_info->licenses_list;
150             }
151             else if (string_is_equal(pValue, "is_experimental"))
152                pCtx->current_entry_bool_val  = &pCtx->core_info->is_experimental;
153             break;
154          case 'n':
155             if (string_is_equal(pValue,      "notes"))
156             {
157                pCtx->current_string_val      = &pCtx->core_info->notes;
158                pCtx->current_string_list_val = &pCtx->core_info->note_list;
159             }
160             break;
161          case 'p':
162             if (string_is_equal(pValue,      "path"))
163                pCtx->current_string_val      = &pCtx->core_info->path;
164             else if (string_is_equal(pValue, "permissions"))
165             {
166                pCtx->current_string_val      = &pCtx->core_info->permissions;
167                pCtx->current_string_list_val = &pCtx->core_info->permissions_list;
168             }
169             break;
170          case 'r':
171             if (string_is_equal(pValue,      "required_hw_api"))
172             {
173                pCtx->current_string_val      = &pCtx->core_info->required_hw_api;
174                pCtx->current_string_list_val = &pCtx->core_info->required_hw_api_list;
175             }
176             break;
177          case 's':
178             if (string_is_equal(pValue,      "system_manufacturer"))
179                pCtx->current_string_val      = &pCtx->core_info->system_manufacturer;
180             else if (string_is_equal(pValue, "systemname"))
181                pCtx->current_string_val      = &pCtx->core_info->systemname;
182             else if (string_is_equal(pValue, "system_id"))
183                pCtx->current_string_val      = &pCtx->core_info->system_id;
184             else if (string_is_equal(pValue, "supported_extensions"))
185             {
186                pCtx->current_string_val      = &pCtx->core_info->supported_extensions;
187                pCtx->current_string_list_val = &pCtx->core_info->supported_extensions_list;
188             }
189             else if (string_is_equal(pValue, "supports_no_game"))
190                pCtx->current_entry_bool_val  = &pCtx->core_info->supports_no_game;
191             break;
192       }
193    }
194    else if ((pCtx->object_depth == 3) && (pCtx->array_depth == 1) && length)
195    {
196       pCtx->current_string_val      = NULL;
197       pCtx->current_entry_uint_val  = NULL;
198 
199       if (pCtx->to_core_file_id)
200       {
201          if (string_is_equal(pValue,         "str"))
202             pCtx->current_string_val         = &pCtx->core_info->core_file_id.str;
203          else if (string_is_equal(pValue,    "hash"))
204             pCtx->current_entry_uint_val     = &pCtx->core_info->core_file_id.hash;
205       }
206    }
207    else if ((pCtx->object_depth == 3) && (pCtx->array_depth == 2) && length)
208    {
209       pCtx->current_string_val      = NULL;
210       pCtx->current_entry_bool_val  = NULL;
211 
212       if (pCtx->to_firmware && (pCtx->core_info->firmware_count > 0))
213       {
214          size_t firmware_idx = pCtx->core_info->firmware_count - 1;
215 
216          if (string_is_equal(pValue,         "path"))
217             pCtx->current_string_val         = &pCtx->core_info->firmware[firmware_idx].path;
218          else if (string_is_equal(pValue,    "desc"))
219             pCtx->current_string_val         = &pCtx->core_info->firmware[firmware_idx].desc;
220          else if (string_is_equal(pValue,    "optional"))
221             pCtx->current_entry_bool_val     = &pCtx->core_info->firmware[firmware_idx].optional;
222       }
223    }
224 
225    return true;
226 }
227 
CCJSONStringHandler(void * context,const char * pValue,size_t length)228 static bool CCJSONStringHandler(void *context, const char *pValue, size_t length)
229 {
230    CCJSONContext *pCtx = (CCJSONContext*)context;
231 
232    if (pCtx->current_string_val && length && !string_is_empty(pValue))
233    {
234       if (*pCtx->current_string_val)
235          free(*pCtx->current_string_val);
236       *pCtx->current_string_val = strdup(pValue);
237 
238       if (pCtx->current_string_list_val)
239       {
240          if (*pCtx->current_string_list_val)
241             string_list_free(*pCtx->current_string_list_val);
242          *pCtx->current_string_list_val = string_split(*pCtx->current_string_val, "|");
243       }
244    }
245 
246    pCtx->current_string_val      = NULL;
247    pCtx->current_string_list_val = NULL;
248 
249    return true;
250 }
251 
CCJSONNumberHandler(void * context,const char * pValue,size_t length)252 static bool CCJSONNumberHandler(void *context, const char *pValue, size_t length)
253 {
254    CCJSONContext *pCtx = (CCJSONContext*)context;
255 
256    if (pCtx->current_entry_uint_val)
257       *pCtx->current_entry_uint_val = string_to_unsigned(pValue);
258 
259    pCtx->current_entry_uint_val = NULL;
260 
261    return true;
262 }
263 
CCJSONBoolHandler(void * context,bool value)264 static bool CCJSONBoolHandler(void *context, bool value)
265 {
266    CCJSONContext *pCtx = (CCJSONContext *)context;
267 
268    if (pCtx->current_entry_bool_val)
269       *pCtx->current_entry_bool_val = value;
270 
271    pCtx->current_entry_bool_val = NULL;
272 
273    return true;
274 }
275 
CCJSONStartObjectHandler(void * context)276 static bool CCJSONStartObjectHandler(void *context)
277 {
278    CCJSONContext *pCtx = (CCJSONContext*)context;
279 
280    pCtx->object_depth++;
281 
282    if ((pCtx->object_depth == 1) && (pCtx->array_depth == 0))
283    {
284       if (pCtx->core_info_cache_list)
285          return false;
286 
287       pCtx->core_info_cache_list = core_info_cache_list_new();
288       if (!pCtx->core_info_cache_list)
289          return false;
290    }
291    else if ((pCtx->object_depth == 2) && (pCtx->array_depth == 1))
292    {
293       if (pCtx->core_info)
294       {
295          core_info_free(pCtx->core_info);
296          free(pCtx->core_info);
297          pCtx->core_info = NULL;
298       }
299 
300       pCtx->core_info = (core_info_t*)calloc(1, sizeof(core_info_t));
301       if (!pCtx->core_info)
302          return false;
303    }
304    else if ((pCtx->object_depth == 3) && (pCtx->array_depth == 2))
305    {
306       if (pCtx->to_firmware)
307       {
308          size_t new_idx                     = pCtx->core_info->firmware_count;
309          core_info_firmware_t *firmware_tmp = (core_info_firmware_t*)
310                realloc(pCtx->core_info->firmware,
311                      (pCtx->core_info->firmware_count + 1) * sizeof(core_info_firmware_t));
312 
313          if (!firmware_tmp)
314             return false;
315 
316          firmware_tmp[new_idx].path     = NULL;
317          firmware_tmp[new_idx].desc     = NULL;
318          firmware_tmp[new_idx].missing  = false;
319          firmware_tmp[new_idx].optional = false;
320 
321          pCtx->core_info->firmware      = firmware_tmp;
322          pCtx->core_info->firmware_count++;
323       }
324    }
325 
326    return true;
327 }
328 
CCJSONEndObjectHandler(void * context)329 static bool CCJSONEndObjectHandler(void *context)
330 {
331    CCJSONContext *pCtx = (CCJSONContext*)context;
332 
333    if ((pCtx->object_depth == 2) && (pCtx->array_depth == 1) && pCtx->core_info)
334    {
335       core_info_cache_add(pCtx->core_info_cache_list, pCtx->core_info, true);
336       free(pCtx->core_info);
337       pCtx->core_info = NULL;
338    }
339    else if ((pCtx->object_depth == 3) && (pCtx->array_depth == 1))
340       pCtx->to_core_file_id = false;
341 
342    retro_assert(pCtx->object_depth > 0);
343    pCtx->object_depth--;
344 
345    return true;
346 }
347 
CCJSONStartArrayHandler(void * context)348 static bool CCJSONStartArrayHandler(void *context)
349 {
350    CCJSONContext *pCtx = (CCJSONContext*)context;
351    pCtx->array_depth++;
352    return true;
353 }
354 
CCJSONEndArrayHandler(void * context)355 static bool CCJSONEndArrayHandler(void *context)
356 {
357    CCJSONContext *pCtx = (CCJSONContext*)context;
358 
359    if ((pCtx->object_depth == 2) && (pCtx->array_depth == 2))
360       pCtx->to_firmware = false;
361 
362    retro_assert(pCtx->array_depth > 0);
363    pCtx->array_depth--;
364 
365    return true;
366 }
367 
368 /* JSON Handlers END */
369 
370 /* Note: 'dst' must be zero initialised, or memory
371  * leaks will occur */
core_info_copy(core_info_t * src,core_info_t * dst)372 static void core_info_copy(core_info_t *src, core_info_t *dst)
373 {
374    dst->path                      = src->path                 ? strdup(src->path)                 : NULL;
375    dst->display_name              = src->display_name         ? strdup(src->display_name)         : NULL;
376    dst->display_version           = src->display_version      ? strdup(src->display_version)      : NULL;
377    dst->core_name                 = src->core_name            ? strdup(src->core_name)            : NULL;
378    dst->system_manufacturer       = src->system_manufacturer  ? strdup(src->system_manufacturer)  : NULL;
379    dst->systemname                = src->systemname           ? strdup(src->systemname)           : NULL;
380    dst->system_id                 = src->system_id            ? strdup(src->system_id)            : NULL;
381    dst->supported_extensions      = src->supported_extensions ? strdup(src->supported_extensions) : NULL;
382    dst->authors                   = src->authors              ? strdup(src->authors)              : NULL;
383    dst->permissions               = src->permissions          ? strdup(src->permissions)          : NULL;
384    dst->licenses                  = src->licenses             ? strdup(src->licenses)             : NULL;
385    dst->categories                = src->categories           ? strdup(src->categories)           : NULL;
386    dst->databases                 = src->databases            ? strdup(src->databases)            : NULL;
387    dst->notes                     = src->notes                ? strdup(src->notes)                : NULL;
388    dst->required_hw_api           = src->required_hw_api      ? strdup(src->required_hw_api)      : NULL;
389    dst->description               = src->description          ? strdup(src->description)          : NULL;
390 
391    dst->categories_list           = src->categories_list           ? string_list_clone(src->categories_list)           : NULL;
392    dst->databases_list            = src->databases_list            ? string_list_clone(src->databases_list)            : NULL;
393    dst->note_list                 = src->note_list                 ? string_list_clone(src->note_list)                 : NULL;
394    dst->supported_extensions_list = src->supported_extensions_list ? string_list_clone(src->supported_extensions_list) : NULL;
395    dst->authors_list              = src->authors_list              ? string_list_clone(src->authors_list)              : NULL;
396    dst->permissions_list          = src->permissions_list          ? string_list_clone(src->permissions_list)          : NULL;
397    dst->licenses_list             = src->licenses_list             ? string_list_clone(src->licenses_list)             : NULL;
398    dst->required_hw_api_list      = src->required_hw_api_list      ? string_list_clone(src->required_hw_api_list)      : NULL;
399 
400    if (src->firmware_count > 0)
401    {
402       dst->firmware = (core_info_firmware_t*)calloc(src->firmware_count,
403             sizeof(core_info_firmware_t));
404 
405       if (dst->firmware)
406       {
407          size_t i;
408 
409          dst->firmware_count = src->firmware_count;
410 
411          for (i = 0; i < src->firmware_count; i++)
412          {
413             dst->firmware[i].path     = src->firmware[i].path ? strdup(src->firmware[i].path) : NULL;
414             dst->firmware[i].desc     = src->firmware[i].desc ? strdup(src->firmware[i].desc) : NULL;
415             dst->firmware[i].missing  = src->firmware[i].missing;
416             dst->firmware[i].optional = src->firmware[i].optional;
417          }
418       }
419       else
420          dst->firmware_count = 0;
421    }
422 
423    dst->core_file_id.str  = src->core_file_id.str ? strdup(src->core_file_id.str) : NULL;
424    dst->core_file_id.hash = src->core_file_id.hash;
425 
426    dst->has_info                      = src->has_info;
427    dst->supports_no_game              = src->supports_no_game;
428    dst->database_match_archive_member = src->database_match_archive_member;
429    dst->is_experimental               = src->is_experimental;
430    dst->is_locked                     = src->is_locked;
431    dst->is_installed                  = src->is_installed;
432 }
433 
434 /* Like core_info_copy, but transfers 'ownership'
435  * of internal objects/data structures from 'src'
436  * to 'dst' */
core_info_transfer(core_info_t * src,core_info_t * dst)437 static void core_info_transfer(core_info_t *src, core_info_t *dst)
438 {
439    dst->path                      = src->path;
440    src->path                      = NULL;
441 
442    dst->display_name              = src->display_name;
443    src->display_name              = NULL;
444 
445    dst->display_version           = src->display_version;
446    src->display_version           = NULL;
447 
448    dst->core_name                 = src->core_name;
449    src->core_name                 = NULL;
450 
451    dst->system_manufacturer       = src->system_manufacturer;
452    src->system_manufacturer       = NULL;
453 
454    dst->systemname                = src->systemname;
455    src->systemname                = NULL;
456 
457    dst->system_id                 = src->system_id;
458    src->system_id                 = NULL;
459 
460    dst->supported_extensions      = src->supported_extensions;
461    src->supported_extensions      = NULL;
462 
463    dst->authors                   = src->authors;
464    src->authors                   = NULL;
465 
466    dst->permissions               = src->permissions;
467    src->permissions               = NULL;
468 
469    dst->licenses                  = src->licenses;
470    src->licenses                  = NULL;
471 
472    dst->categories                = src->categories;
473    src->categories                = NULL;
474 
475    dst->databases                 = src->databases;
476    src->databases                 = NULL;
477 
478    dst->notes                     = src->notes;
479    src->notes                     = NULL;
480 
481    dst->required_hw_api           = src->required_hw_api;
482    src->required_hw_api           = NULL;
483 
484    dst->description               = src->description;
485    src->description               = NULL;
486 
487    dst->categories_list           = src->categories_list;
488    src->categories_list           = NULL;
489 
490    dst->databases_list            = src->databases_list;
491    src->databases_list            = NULL;
492 
493    dst->note_list                 = src->note_list;
494    src->note_list                 = NULL;
495 
496    dst->supported_extensions_list = src->supported_extensions_list;
497    src->supported_extensions_list = NULL;
498 
499    dst->authors_list              = src->authors_list;
500    src->authors_list              = NULL;
501 
502    dst->permissions_list          = src->permissions_list;
503    src->permissions_list          = NULL;
504 
505    dst->licenses_list             = src->licenses_list;
506    src->licenses_list             = NULL;
507 
508    dst->required_hw_api_list      = src->required_hw_api_list;
509    src->required_hw_api_list      = NULL;
510 
511    dst->firmware                  = src->firmware;
512    dst->firmware_count            = src->firmware_count;
513    src->firmware                  = NULL;
514    src->firmware_count            = 0;
515 
516    dst->core_file_id.str          = src->core_file_id.str;
517    src->core_file_id.str          = NULL;
518    dst->core_file_id.hash         = src->core_file_id.hash;
519 
520    dst->has_info                      = src->has_info;
521    dst->supports_no_game              = src->supports_no_game;
522    dst->database_match_archive_member = src->database_match_archive_member;
523    dst->is_experimental               = src->is_experimental;
524    dst->is_locked                     = src->is_locked;
525    dst->is_installed                  = src->is_installed;
526 }
527 
core_info_cache_list_free(core_info_cache_list_t * core_info_cache_list)528 static void core_info_cache_list_free(core_info_cache_list_t *core_info_cache_list)
529 {
530    size_t i;
531 
532    if (!core_info_cache_list)
533       return;
534 
535    for (i = 0; i < core_info_cache_list->length; i++)
536    {
537       core_info_t* info = (core_info_t*)&core_info_cache_list->items[i];
538       core_info_free(info);
539    }
540 
541    free(core_info_cache_list->items);
542    free(core_info_cache_list);
543 }
544 
core_info_cache_list_new(void)545 static core_info_cache_list_t *core_info_cache_list_new(void)
546 {
547    core_info_cache_list_t *core_info_cache_list = NULL;
548 
549    core_info_cache_list = (core_info_cache_list_t *)malloc(sizeof(*core_info_cache_list));
550    if (!core_info_cache_list)
551       return NULL;
552 
553    core_info_cache_list->length = 0;
554    core_info_cache_list->items  = (core_info_t *)calloc(CORE_INFO_CACHE_DEFAULT_CAPACITY,
555          sizeof(core_info_t));
556 
557    if (!core_info_cache_list->items)
558    {
559       core_info_cache_list_free(core_info_cache_list);
560       return NULL;
561    }
562 
563    core_info_cache_list->capacity = CORE_INFO_CACHE_DEFAULT_CAPACITY;
564    core_info_cache_list->refresh  = false;
565 
566    return core_info_cache_list;
567 }
568 
core_info_cache_find(core_info_cache_list_t * list,char * core_file_id)569 static core_info_t *core_info_cache_find(core_info_cache_list_t *list, char *core_file_id)
570 {
571    uint32_t hash;
572    size_t i;
573 
574    if (!list ||
575        string_is_empty(core_file_id))
576       return NULL;
577 
578    hash = core_info_hash_string(core_file_id);
579 
580    for (i = 0; i < list->length; i++)
581    {
582       core_info_t *info = (core_info_t*)&list->items[i];
583 
584       if (!info)
585          continue;
586 
587       if ((info->core_file_id.hash == hash) &&
588           string_is_equal(info->core_file_id.str, core_file_id))
589       {
590          info->is_installed = true;
591          return info;
592       }
593    }
594 
595    return NULL;
596 }
597 
core_info_cache_add(core_info_cache_list_t * list,core_info_t * info,bool transfer)598 static void core_info_cache_add(core_info_cache_list_t *list, core_info_t *info,
599       bool transfer)
600 {
601    core_info_t *info_cache = NULL;
602 
603    if (!list ||
604        !info ||
605        (info->core_file_id.hash == 0) ||
606        string_is_empty(info->core_file_id.str))
607       return;
608 
609    if (list->length >= list->capacity)
610    {
611       size_t prev_capacity   = list->capacity;
612       core_info_t *items_tmp = (core_info_t*)realloc(list->items,
613             (list->capacity << 1) * sizeof(core_info_t));
614 
615       if (!items_tmp)
616          return;
617 
618       list->capacity = list->capacity << 1;
619       list->items    = items_tmp;
620 
621       memset(&list->items[prev_capacity], 0,
622             (list->capacity - prev_capacity) * sizeof(core_info_t));
623    }
624 
625    info_cache = (core_info_t*)&list->items[list->length];
626 
627    if (transfer)
628       core_info_transfer(info, info_cache);
629    else
630       core_info_copy(info, info_cache);
631 
632    list->length++;
633 }
634 
core_info_cache_read(const char * info_dir)635 static core_info_cache_list_t *core_info_cache_read(const char *info_dir)
636 {
637    intfstream_t *file                           = NULL;
638    rjson_t *parser                              = NULL;
639    CCJSONContext context                        = {0};
640    core_info_cache_list_t *core_info_cache_list = NULL;
641    char file_path[PATH_MAX_LENGTH];
642 
643    /* Check whether a 'force refresh' file
644     * is present */
645    file_path[0] = '\0';
646 
647    if (string_is_empty(info_dir))
648       strlcpy(file_path, FILE_PATH_CORE_INFO_CACHE_REFRESH, sizeof(file_path));
649    else
650       fill_pathname_join(file_path, info_dir, FILE_PATH_CORE_INFO_CACHE_REFRESH,
651             sizeof(file_path));
652 
653    if (path_is_valid(file_path))
654       return core_info_cache_list_new();
655 
656    /* Open info cache file */
657    file_path[0] = '\0';
658 
659    if (string_is_empty(info_dir))
660       strlcpy(file_path, FILE_PATH_CORE_INFO_CACHE, sizeof(file_path));
661    else
662       fill_pathname_join(file_path, info_dir, FILE_PATH_CORE_INFO_CACHE,
663             sizeof(file_path));
664 
665 #if defined(HAVE_ZLIB)
666    file = intfstream_open_rzip_file(file_path,
667          RETRO_VFS_FILE_ACCESS_READ);
668 #else
669    file = intfstream_open_file(file_path,
670          RETRO_VFS_FILE_ACCESS_READ,
671          RETRO_VFS_FILE_ACCESS_HINT_NONE);
672 #endif
673 
674    if (!file)
675       return core_info_cache_list_new();
676 
677    /* Parse info cache file */
678    parser = rjson_open_stream(file);
679    if (!parser)
680    {
681       RARCH_ERR("[Core Info] Failed to create JSON parser\n");
682       goto end;
683    }
684 
685    rjson_set_options(parser,
686            RJSON_OPTION_ALLOW_UTF8BOM
687          | RJSON_OPTION_ALLOW_COMMENTS
688          | RJSON_OPTION_ALLOW_UNESCAPED_CONTROL_CHARACTERS
689          | RJSON_OPTION_REPLACE_INVALID_ENCODING);
690 
691    if (rjson_parse(parser, &context,
692          CCJSONObjectMemberHandler,
693          CCJSONStringHandler,
694          CCJSONNumberHandler,
695          CCJSONStartObjectHandler,
696          CCJSONEndObjectHandler,
697          CCJSONStartArrayHandler,
698          CCJSONEndArrayHandler,
699          CCJSONBoolHandler,
700          NULL) /* Unused null handler */
701          != RJSON_DONE)
702    {
703       RARCH_WARN("[Core Info] Error parsing chunk:\n---snip---\n%.*s\n---snip---\n",
704             rjson_get_source_context_len(parser),
705             rjson_get_source_context_buf(parser));
706       RARCH_WARN("[Core Info] Error: Invalid JSON at line %d, column %d - %s.\n",
707             (int)rjson_get_source_line(parser),
708             (int)rjson_get_source_column(parser),
709             (*rjson_get_error(parser) ? rjson_get_error(parser) : "format error"));
710 
711       /* Info cache is corrupt - discard it */
712       core_info_cache_list_free(context.core_info_cache_list);
713       core_info_cache_list = core_info_cache_list_new();
714    }
715    else
716       core_info_cache_list = context.core_info_cache_list;
717 
718    rjson_free(parser);
719 
720    /* Clean up leftovers in the event of
721     * a parsing error */
722    if (context.core_info)
723    {
724       core_info_free(context.core_info);
725       free(context.core_info);
726    }
727 
728 end:
729    intfstream_close(file);
730    free(file);
731 
732    return core_info_cache_list;
733 }
734 
core_info_cache_write(core_info_cache_list_t * list,const char * info_dir)735 static bool core_info_cache_write(core_info_cache_list_t *list, const char *info_dir)
736 {
737    intfstream_t *file    = NULL;
738    rjsonwriter_t *writer = NULL;
739    bool success          = false;
740    char file_path[PATH_MAX_LENGTH];
741    size_t i, j;
742 
743    file_path[0] = '\0';
744 
745    if (!list)
746       return false;
747 
748    /* Open info cache file */
749    if (string_is_empty(info_dir))
750       strlcpy(file_path, FILE_PATH_CORE_INFO_CACHE, sizeof(file_path));
751    else
752       fill_pathname_join(file_path, info_dir, FILE_PATH_CORE_INFO_CACHE,
753             sizeof(file_path));
754 
755 #if defined(CORE_INFO_CACHE_COMPRESS)
756    file = intfstream_open_rzip_file(file_path,
757          RETRO_VFS_FILE_ACCESS_WRITE);
758 #else
759    file = intfstream_open_file(file_path,
760          RETRO_VFS_FILE_ACCESS_WRITE,
761          RETRO_VFS_FILE_ACCESS_HINT_NONE);
762 #endif
763 
764    if (!file)
765    {
766       RARCH_ERR("[Core Info] Failed to write to core info cache file: %s\n", file_path);
767       return false;
768    }
769 
770    /* Write info cache */
771    writer = rjsonwriter_open_stream(file);
772    if (!writer)
773    {
774       RARCH_ERR("[Core Info] Failed to create JSON writer\n");
775       goto end;
776    }
777 
778 #if defined(CORE_INFO_CACHE_COMPRESS)
779    /* When compressing info cache, human readability
780     * is not a factor - can skip all indentation
781     * and new line characters */
782    rjsonwriter_set_options(writer, RJSONWRITER_OPTION_SKIP_WHITESPACE);
783 #endif
784 
785    rjsonwriter_add_start_object(writer);
786    rjsonwriter_add_newline(writer);
787    rjsonwriter_add_spaces(writer, 2);
788    rjsonwriter_add_string(writer, "version");
789    rjsonwriter_add_colon(writer);
790    rjsonwriter_add_space(writer);
791    rjsonwriter_add_string(writer, "1.0");
792    rjsonwriter_add_comma(writer);
793    rjsonwriter_add_newline(writer);
794    rjsonwriter_add_spaces(writer, 2);
795    rjsonwriter_add_string(writer, "items");
796    rjsonwriter_add_colon(writer);
797    rjsonwriter_add_space(writer);
798    rjsonwriter_add_start_array(writer);
799    rjsonwriter_add_newline(writer);
800 
801    for (i = 0; i < list->length; i++)
802    {
803       core_info_t* info = &list->items[i];
804 
805       if (!info || !info->is_installed)
806          continue;
807 
808       if (i > 0)
809       {
810          rjsonwriter_add_comma(writer);
811          rjsonwriter_add_newline(writer);
812       }
813 
814       rjsonwriter_add_spaces(writer, 4);
815       rjsonwriter_add_start_object(writer);
816       rjsonwriter_add_newline(writer);
817 
818       rjsonwriter_add_spaces(writer, 6);
819       rjsonwriter_add_string(writer, "path");
820       rjsonwriter_add_colon(writer);
821       rjsonwriter_add_space(writer);
822       rjsonwriter_add_string(writer, info->path);
823       rjsonwriter_add_comma(writer);
824       rjsonwriter_add_newline(writer);
825 
826       rjsonwriter_add_spaces(writer, 6);
827       rjsonwriter_add_string(writer, "display_name");
828       rjsonwriter_add_colon(writer);
829       rjsonwriter_add_space(writer);
830       rjsonwriter_add_string(writer, info->display_name);
831       rjsonwriter_add_comma(writer);
832       rjsonwriter_add_newline(writer);
833 
834       rjsonwriter_add_spaces(writer, 6);
835       rjsonwriter_add_string(writer, "display_version");
836       rjsonwriter_add_colon(writer);
837       rjsonwriter_add_space(writer);
838       rjsonwriter_add_string(writer, info->display_version);
839       rjsonwriter_add_comma(writer);
840       rjsonwriter_add_newline(writer);
841 
842       rjsonwriter_add_spaces(writer, 6);
843       rjsonwriter_add_string(writer, "core_name");
844       rjsonwriter_add_colon(writer);
845       rjsonwriter_add_space(writer);
846       rjsonwriter_add_string(writer, info->core_name);
847       rjsonwriter_add_comma(writer);
848       rjsonwriter_add_newline(writer);
849 
850       rjsonwriter_add_spaces(writer, 6);
851       rjsonwriter_add_string(writer, "system_manufacturer");
852       rjsonwriter_add_colon(writer);
853       rjsonwriter_add_space(writer);
854       rjsonwriter_add_string(writer, info->system_manufacturer);
855       rjsonwriter_add_comma(writer);
856       rjsonwriter_add_newline(writer);
857 
858       rjsonwriter_add_spaces(writer, 6);
859       rjsonwriter_add_string(writer, "systemname");
860       rjsonwriter_add_colon(writer);
861       rjsonwriter_add_space(writer);
862       rjsonwriter_add_string(writer, info->systemname);
863       rjsonwriter_add_comma(writer);
864       rjsonwriter_add_newline(writer);
865 
866       rjsonwriter_add_spaces(writer, 6);
867       rjsonwriter_add_string(writer, "system_id");
868       rjsonwriter_add_colon(writer);
869       rjsonwriter_add_space(writer);
870       rjsonwriter_add_string(writer, info->system_id);
871       rjsonwriter_add_comma(writer);
872       rjsonwriter_add_newline(writer);
873 
874       rjsonwriter_add_spaces(writer, 6);
875       rjsonwriter_add_string(writer, "supported_extensions");
876       rjsonwriter_add_colon(writer);
877       rjsonwriter_add_space(writer);
878       rjsonwriter_add_string(writer, info->supported_extensions);
879       rjsonwriter_add_comma(writer);
880       rjsonwriter_add_newline(writer);
881 
882       rjsonwriter_add_spaces(writer, 6);
883       rjsonwriter_add_string(writer, "authors");
884       rjsonwriter_add_colon(writer);
885       rjsonwriter_add_space(writer);
886       rjsonwriter_add_string(writer, info->authors);
887       rjsonwriter_add_comma(writer);
888       rjsonwriter_add_newline(writer);
889 
890       rjsonwriter_add_spaces(writer, 6);
891       rjsonwriter_add_string(writer, "permissions");
892       rjsonwriter_add_colon(writer);
893       rjsonwriter_add_space(writer);
894       rjsonwriter_add_string(writer, info->permissions);
895       rjsonwriter_add_comma(writer);
896       rjsonwriter_add_newline(writer);
897 
898       rjsonwriter_add_spaces(writer, 6);
899       rjsonwriter_add_string(writer, "licenses");
900       rjsonwriter_add_colon(writer);
901       rjsonwriter_add_space(writer);
902       rjsonwriter_add_string(writer, info->licenses);
903       rjsonwriter_add_comma(writer);
904       rjsonwriter_add_newline(writer);
905 
906       rjsonwriter_add_spaces(writer, 6);
907       rjsonwriter_add_string(writer, "categories");
908       rjsonwriter_add_colon(writer);
909       rjsonwriter_add_space(writer);
910       rjsonwriter_add_string(writer, info->categories);
911       rjsonwriter_add_comma(writer);
912       rjsonwriter_add_newline(writer);
913 
914       rjsonwriter_add_spaces(writer, 6);
915       rjsonwriter_add_string(writer, "databases");
916       rjsonwriter_add_colon(writer);
917       rjsonwriter_add_space(writer);
918       rjsonwriter_add_string(writer, info->databases);
919       rjsonwriter_add_comma(writer);
920       rjsonwriter_add_newline(writer);
921 
922       rjsonwriter_add_spaces(writer, 6);
923       rjsonwriter_add_string(writer, "notes");
924       rjsonwriter_add_colon(writer);
925       rjsonwriter_add_space(writer);
926       rjsonwriter_add_string(writer, info->notes);
927       rjsonwriter_add_comma(writer);
928       rjsonwriter_add_newline(writer);
929 
930       rjsonwriter_add_spaces(writer, 6);
931       rjsonwriter_add_string(writer, "required_hw_api");
932       rjsonwriter_add_colon(writer);
933       rjsonwriter_add_space(writer);
934       rjsonwriter_add_string(writer, info->required_hw_api);
935       rjsonwriter_add_comma(writer);
936       rjsonwriter_add_newline(writer);
937 
938       rjsonwriter_add_spaces(writer, 6);
939       rjsonwriter_add_string(writer, "description");
940       rjsonwriter_add_colon(writer);
941       rjsonwriter_add_space(writer);
942       rjsonwriter_add_string(writer, info->description);
943       rjsonwriter_add_comma(writer);
944       rjsonwriter_add_newline(writer);
945 
946       if (info->firmware_count > 0)
947       {
948          rjsonwriter_add_spaces(writer, 6);
949          rjsonwriter_add_string(writer, "firmware");
950          rjsonwriter_add_colon(writer);
951          rjsonwriter_add_space(writer);
952          rjsonwriter_add_start_array(writer);
953          rjsonwriter_add_newline(writer);
954 
955          for (j = 0; j < info->firmware_count; j++)
956          {
957             rjsonwriter_add_spaces(writer, 8);
958             rjsonwriter_add_start_object(writer);
959             rjsonwriter_add_newline(writer);
960             rjsonwriter_add_spaces(writer, 10);
961             rjsonwriter_add_string(writer, "path");
962             rjsonwriter_add_colon(writer);
963             rjsonwriter_add_space(writer);
964             rjsonwriter_add_string(writer, info->firmware[j].path);
965             rjsonwriter_add_comma(writer);
966             rjsonwriter_add_newline(writer);
967             rjsonwriter_add_spaces(writer, 10);
968             rjsonwriter_add_string(writer, "desc");
969             rjsonwriter_add_colon(writer);
970             rjsonwriter_add_space(writer);
971             rjsonwriter_add_string(writer, info->firmware[j].desc);
972             rjsonwriter_add_comma(writer);
973             rjsonwriter_add_newline(writer);
974             rjsonwriter_add_spaces(writer, 10);
975             rjsonwriter_add_string(writer, "optional");
976             rjsonwriter_add_colon(writer);
977             rjsonwriter_add_space(writer);
978             rjsonwriter_add_bool(writer, info->firmware[j].optional);
979             rjsonwriter_add_newline(writer);
980             rjsonwriter_add_spaces(writer, 8);
981             rjsonwriter_add_end_object(writer);
982 
983             if (j < info->firmware_count - 1)
984                rjsonwriter_add_comma(writer);
985 
986             rjsonwriter_add_newline(writer);
987          }
988 
989          rjsonwriter_add_spaces(writer, 6);
990          rjsonwriter_add_end_array(writer);
991          rjsonwriter_add_comma(writer);
992          rjsonwriter_add_newline(writer);
993       }
994 
995       rjsonwriter_add_spaces(writer, 6);
996       rjsonwriter_add_string(writer, "core_file_id");
997       rjsonwriter_add_colon(writer);
998       rjsonwriter_add_newline(writer);
999       rjsonwriter_add_spaces(writer, 6);
1000       rjsonwriter_add_start_object(writer);
1001       rjsonwriter_add_newline(writer);
1002       rjsonwriter_add_spaces(writer, 8);
1003       rjsonwriter_add_string(writer, "str");
1004       rjsonwriter_add_colon(writer);
1005       rjsonwriter_add_space(writer);
1006       rjsonwriter_add_string(writer, info->core_file_id.str);
1007       rjsonwriter_add_comma(writer);
1008       rjsonwriter_add_newline(writer);
1009       rjsonwriter_add_spaces(writer, 8);
1010       rjsonwriter_add_string(writer, "hash");
1011       rjsonwriter_add_colon(writer);
1012       rjsonwriter_add_space(writer);
1013       rjsonwriter_add_unsigned(writer, info->core_file_id.hash);
1014       rjsonwriter_add_newline(writer);
1015       rjsonwriter_add_spaces(writer, 6);
1016       rjsonwriter_add_end_object(writer);
1017       rjsonwriter_add_comma(writer);
1018       rjsonwriter_add_newline(writer);
1019 
1020       rjsonwriter_add_spaces(writer, 6);
1021       rjsonwriter_add_string(writer, "firmware_count");
1022       rjsonwriter_add_colon(writer);
1023       rjsonwriter_add_space(writer);
1024       rjsonwriter_add_unsigned(writer, info->firmware_count);
1025       rjsonwriter_add_comma(writer);
1026       rjsonwriter_add_newline(writer);
1027 
1028       rjsonwriter_add_spaces(writer, 6);
1029       rjsonwriter_add_string(writer, "has_info");
1030       rjsonwriter_add_colon(writer);
1031       rjsonwriter_add_space(writer);
1032       rjsonwriter_add_bool(writer, info->has_info);
1033       rjsonwriter_add_comma(writer);
1034       rjsonwriter_add_newline(writer);
1035 
1036       rjsonwriter_add_spaces(writer, 6);
1037       rjsonwriter_add_string(writer, "supports_no_game");
1038       rjsonwriter_add_colon(writer);
1039       rjsonwriter_add_space(writer);
1040       rjsonwriter_add_bool(writer, info->supports_no_game);
1041       rjsonwriter_add_comma(writer);
1042       rjsonwriter_add_newline(writer);
1043 
1044       rjsonwriter_add_spaces(writer, 6);
1045       rjsonwriter_add_string(writer, "database_match_archive_member");
1046       rjsonwriter_add_colon(writer);
1047       rjsonwriter_add_space(writer);
1048       rjsonwriter_add_bool(writer, info->database_match_archive_member);
1049       rjsonwriter_add_comma(writer);
1050       rjsonwriter_add_newline(writer);
1051 
1052       rjsonwriter_add_spaces(writer, 6);
1053       rjsonwriter_add_string(writer, "is_experimental");
1054       rjsonwriter_add_colon(writer);
1055       rjsonwriter_add_space(writer);
1056       rjsonwriter_add_bool(writer, info->is_experimental);
1057       rjsonwriter_add_newline(writer);
1058 
1059       rjsonwriter_add_spaces(writer, 4);
1060       rjsonwriter_add_end_object(writer);
1061    }
1062 
1063    rjsonwriter_add_newline(writer);
1064    rjsonwriter_add_spaces(writer, 2);
1065    rjsonwriter_add_end_array(writer);
1066    rjsonwriter_add_newline(writer);
1067    rjsonwriter_add_end_object(writer);
1068    rjsonwriter_add_newline(writer);
1069    rjsonwriter_free(writer);
1070 
1071    RARCH_LOG("[Core Info] Wrote to cache file: %s\n", file_path);
1072    success = true;
1073 
1074    /* Remove 'force refresh' file, if required */
1075    file_path[0] = '\0';
1076 
1077    if (string_is_empty(info_dir))
1078       strlcpy(file_path, FILE_PATH_CORE_INFO_CACHE_REFRESH, sizeof(file_path));
1079    else
1080       fill_pathname_join(file_path, info_dir, FILE_PATH_CORE_INFO_CACHE_REFRESH,
1081             sizeof(file_path));
1082 
1083    if (path_is_valid(file_path))
1084       filestream_delete(file_path);
1085 
1086 end:
1087    intfstream_close(file);
1088    free(file);
1089 
1090    list->refresh = false;
1091    return success;
1092 }
1093 
core_info_check_uninstalled(core_info_cache_list_t * list)1094 static void core_info_check_uninstalled(core_info_cache_list_t *list)
1095 {
1096    size_t i;
1097 
1098    if (!list)
1099       return;
1100 
1101    for (i = 0; i < list->length; i++)
1102    {
1103       core_info_t *info = (core_info_t *)&list->items[i];
1104 
1105       if (!info)
1106          continue;
1107 
1108       if (!info->is_installed)
1109       {
1110          list->refresh = true;
1111          return;
1112       }
1113    }
1114 }
1115 
1116 /* When called, generates a temporary file
1117  * that will force an info cache refresh the
1118  * next time that core info is initialised with
1119  * caching enabled */
core_info_cache_force_refresh(const char * path_info)1120 bool core_info_cache_force_refresh(const char *path_info)
1121 {
1122    char file_path[PATH_MAX_LENGTH];
1123 
1124    file_path[0] = '\0';
1125 
1126    /* Get 'force refresh' file path */
1127    if (string_is_empty(path_info))
1128       strlcpy(file_path, FILE_PATH_CORE_INFO_CACHE_REFRESH, sizeof(file_path));
1129    else
1130       fill_pathname_join(file_path, path_info, FILE_PATH_CORE_INFO_CACHE_REFRESH,
1131             sizeof(file_path));
1132 
1133    /* Generate a new, empty 'force refresh' file,
1134     * if required */
1135    if (!path_is_valid(file_path))
1136    {
1137       RFILE *refresh_file = filestream_open(
1138             file_path,
1139             RETRO_VFS_FILE_ACCESS_WRITE,
1140             RETRO_VFS_FILE_ACCESS_HINT_NONE);
1141 
1142       if (!refresh_file)
1143          return false;
1144 
1145       /* We have to write something - just output
1146        * a single character */
1147       if (filestream_putc(refresh_file, 0) != 0)
1148       {
1149          filestream_close(refresh_file);
1150          return false;
1151       }
1152 
1153       filestream_close(refresh_file);
1154    }
1155 
1156    return true;
1157 }
1158 
1159 /***********************/
1160 /* Core Info Cache END */
1161 /***********************/
1162 
1163 enum compare_op
1164 {
1165    COMPARE_OP_EQUAL = 0,
1166    COMPARE_OP_NOT_EQUAL,
1167    COMPARE_OP_LESS,
1168    COMPARE_OP_LESS_EQUAL,
1169    COMPARE_OP_GREATER,
1170    COMPARE_OP_GREATER_EQUAL
1171 };
1172 
1173 typedef struct
1174 {
1175    const char *path;
1176    const char *filename;
1177 } core_file_path_t;
1178 
1179 typedef struct
1180 {
1181    core_file_path_t *list;
1182    size_t size;
1183 } core_file_path_list_t;
1184 
1185 typedef struct
1186 {
1187    const char *filename;
1188    uint32_t hash;
1189 } core_lock_file_path_t;
1190 
1191 typedef struct
1192 {
1193    core_lock_file_path_t *list;
1194    size_t size;
1195 } core_lock_file_path_list_t;
1196 
1197 typedef struct
1198 {
1199    struct string_list *dir_list;
1200    core_file_path_list_t *core_list;
1201    core_lock_file_path_list_t *lock_list;
1202 } core_path_list_t;
1203 
core_info_hash_string(const char * str)1204 static uint32_t core_info_hash_string(const char *str)
1205 {
1206    unsigned char c;
1207    uint32_t hash = (uint32_t)0x811c9dc5;
1208    while ((c = (unsigned char)*(str++)) != '\0')
1209       hash = ((hash * (uint32_t)0x01000193) ^ (uint32_t)c);
1210    return (hash ? hash : 1);
1211 }
1212 
core_info_path_list_free(core_path_list_t * path_list)1213 static void core_info_path_list_free(core_path_list_t *path_list)
1214 {
1215    if (!path_list)
1216       return;
1217 
1218    if (path_list->core_list)
1219    {
1220       if (path_list->core_list->list)
1221          free(path_list->core_list->list);
1222       free(path_list->core_list);
1223    }
1224 
1225    if (path_list->lock_list)
1226    {
1227       if (path_list->lock_list->list)
1228          free(path_list->lock_list->list);
1229       free(path_list->lock_list);
1230    }
1231 
1232    if (path_list->dir_list)
1233       string_list_free(path_list->dir_list);
1234 
1235    free(path_list);
1236 }
1237 
core_info_path_list_new(const char * core_dir,const char * core_exts,bool show_hidden_files)1238 static core_path_list_t *core_info_path_list_new(const char *core_dir,
1239       const char *core_exts, bool show_hidden_files)
1240 {
1241    core_path_list_t *path_list       = (core_path_list_t*)
1242          calloc(1, sizeof(*path_list));
1243    struct string_list *core_ext_list = NULL;
1244    bool dir_list_ok                  = false;
1245    char exts[32];
1246    size_t i;
1247 
1248    exts[0] = '\0';
1249 
1250    if (string_is_empty(core_exts) ||
1251        !path_list)
1252       goto error;
1253 
1254    core_ext_list = string_split(core_exts, "|");
1255    if (!core_ext_list)
1256       goto error;
1257 
1258    /* Allocate list containers */
1259    path_list->dir_list  = string_list_new();
1260    path_list->core_list = (core_file_path_list_t*)calloc(1,
1261          sizeof(*path_list->core_list));
1262    path_list->lock_list = (core_lock_file_path_list_t*)calloc(1,
1263          sizeof(*path_list->lock_list));
1264 
1265    if (!path_list->dir_list ||
1266        !path_list->core_list ||
1267        !path_list->lock_list)
1268       goto error;
1269 
1270    /* Get list of file extensions to include
1271     * (core + lock file) */
1272    fill_pathname_join_delim(exts, core_exts, FILE_PATH_LOCK_EXTENSION_NO_DOT,
1273          '|', sizeof(exts));
1274 
1275    /* Fetch core directory listing */
1276    dir_list_ok = dir_list_append(path_list->dir_list,
1277          core_dir, exts, false, show_hidden_files,
1278                false, false);
1279 
1280 #if defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
1281    {
1282       /* UWP: browse the optional packages for additional cores */
1283       struct string_list core_packages = {0};
1284 
1285       if (string_list_initialize(&core_packages))
1286       {
1287          uwp_fill_installed_core_packages(&core_packages);
1288          for (i = 0; i < core_packages.size; i++)
1289             dir_list_append(path_list->dir_list,
1290                   core_packages.elems[i].data, exts, false,
1291                         show_hidden_files, false, false);
1292          string_list_deinitialize(&core_packages);
1293       }
1294    }
1295 #else
1296    /* Keep the old 'directory not found' behaviour */
1297    if (!dir_list_ok)
1298       goto error;
1299 #endif
1300 
1301    /* Allocate sub lists */
1302    path_list->core_list->list = (core_file_path_t*)
1303          malloc(path_list->dir_list->size *
1304                sizeof(*path_list->core_list->list));
1305    path_list->lock_list->list = (core_lock_file_path_t*)
1306          malloc(path_list->dir_list->size *
1307                sizeof(*path_list->lock_list->list));
1308 
1309    if (!path_list->core_list->list ||
1310        !path_list->lock_list->list)
1311       goto error;
1312 
1313    /* Parse directory listing */
1314    for (i = 0; i < path_list->dir_list->size; i++)
1315    {
1316       const char *file_path = path_list->dir_list->elems[i].data;
1317       const char *filename  = NULL;
1318       const char *file_ext  = NULL;
1319 
1320       if (string_is_empty(file_path) ||
1321           !(filename = path_basename_nocompression(file_path)) ||
1322           !(file_ext = path_get_extension(filename)))
1323          continue;
1324 
1325       /* Check whether this is a core or lock file */
1326       if (string_list_find_elem(core_ext_list, file_ext))
1327       {
1328          path_list->core_list->list[
1329                path_list->core_list->size].path     = file_path;
1330          path_list->core_list->list[
1331                path_list->core_list->size].filename = filename;
1332          path_list->core_list->size++;
1333       }
1334       else if (string_is_equal(file_ext, FILE_PATH_LOCK_EXTENSION_NO_DOT))
1335       {
1336          path_list->lock_list->list[
1337                path_list->lock_list->size].filename = filename;
1338          path_list->lock_list->list[
1339                path_list->lock_list->size].hash     = core_info_hash_string(filename);
1340          path_list->lock_list->size++;
1341       }
1342    }
1343 
1344    string_list_free(core_ext_list);
1345    return path_list;
1346 
1347 error:
1348    string_list_free(core_ext_list);
1349    core_info_path_list_free(path_list);
1350    return NULL;
1351 }
1352 
core_info_path_is_locked(core_lock_file_path_list_t * lock_list,const char * core_file_name)1353 static bool core_info_path_is_locked(core_lock_file_path_list_t *lock_list,
1354       const char *core_file_name)
1355 {
1356    size_t i;
1357    uint32_t hash;
1358    char lock_filename[256];
1359 
1360    if (lock_list->size < 1)
1361       return false;
1362 
1363    snprintf(lock_filename, sizeof(lock_filename),
1364          "%s" FILE_PATH_LOCK_EXTENSION, core_file_name);
1365 
1366    hash = core_info_hash_string(lock_filename);
1367 
1368    for (i = 0; i < lock_list->size; i++)
1369    {
1370       core_lock_file_path_t *lock_file = &lock_list->list[i];
1371 
1372       if ((lock_file->hash == hash) &&
1373           string_is_equal(lock_file->filename, lock_filename))
1374          return true;
1375    }
1376 
1377    return false;
1378 }
1379 
core_info_get_file_id(const char * core_filename,char * core_file_id,size_t len)1380 static bool core_info_get_file_id(const char *core_filename,
1381       char *core_file_id, size_t len)
1382 {
1383    char *last_underscore = NULL;
1384 
1385    if (string_is_empty(core_filename))
1386       return false;
1387 
1388    /* Core file 'id' is filename without extension
1389     * or platform-specific suffix */
1390 
1391    /* > Remove extension */
1392    strlcpy(core_file_id, core_filename, len);
1393    path_remove_extension(core_file_id);
1394 
1395    /* > Remove suffix */
1396    last_underscore = (char*)strrchr(core_file_id, '_');
1397 
1398    if (!string_is_empty(last_underscore) &&
1399        !string_is_equal(last_underscore, "_libretro"))
1400       *last_underscore = '\0';
1401 
1402    return !string_is_empty(core_file_id);
1403 }
1404 
core_info_find_internal(core_info_list_t * list,const char * core_path)1405 static core_info_t *core_info_find_internal(
1406       core_info_list_t *list,
1407       const char *core_path)
1408 {
1409    char core_file_id[256];
1410    uint32_t hash;
1411    size_t i;
1412 
1413    core_file_id[0] = '\0';
1414 
1415    if (!list ||
1416        string_is_empty(core_path) ||
1417        !core_info_get_file_id(path_basename_nocompression(core_path),
1418             core_file_id, sizeof(core_file_id)))
1419       return NULL;
1420 
1421    hash = core_info_hash_string(core_file_id);
1422 
1423    for (i = 0; i < list->count; i++)
1424    {
1425       core_info_t *info = &list->list[i];
1426 
1427       if ((info->core_file_id.hash == hash) &&
1428           string_is_equal(info->core_file_id.str, core_file_id))
1429          return info;
1430    }
1431 
1432    return NULL;
1433 }
1434 
core_info_resolve_firmware(core_info_t * info,config_file_t * conf)1435 static void core_info_resolve_firmware(
1436       core_info_t *info, config_file_t *conf)
1437 {
1438    unsigned i;
1439    unsigned firmware_count        = 0;
1440    core_info_firmware_t *firmware = NULL;
1441 
1442    if (!config_get_uint(conf, "firmware_count", &firmware_count))
1443       return;
1444 
1445    firmware = (core_info_firmware_t*)calloc(firmware_count, sizeof(*firmware));
1446 
1447    if (!firmware)
1448       return;
1449 
1450    for (i = 0; i < firmware_count; i++)
1451    {
1452       char path_key[64];
1453       char desc_key[64];
1454       char opt_key[64];
1455       struct config_entry_list *entry = NULL;
1456       bool tmp_bool                   = false;
1457 
1458       path_key[0] = '\0';
1459       desc_key[0] = '\0';
1460       opt_key[0]  = '\0';
1461 
1462       snprintf(path_key, sizeof(path_key), "firmware%u_path", i);
1463       snprintf(desc_key, sizeof(desc_key), "firmware%u_desc", i);
1464       snprintf(opt_key,  sizeof(opt_key),  "firmware%u_opt",  i);
1465 
1466       entry = config_get_entry(conf, path_key);
1467 
1468       if (entry && !string_is_empty(entry->value))
1469       {
1470          firmware[i].path = entry->value;
1471          entry->value     = NULL;
1472       }
1473 
1474       entry = config_get_entry(conf, desc_key);
1475 
1476       if (entry && !string_is_empty(entry->value))
1477       {
1478          firmware[i].desc = entry->value;
1479          entry->value     = NULL;
1480       }
1481 
1482       if (config_get_bool(conf, opt_key , &tmp_bool))
1483          firmware[i].optional = tmp_bool;
1484    }
1485 
1486    info->firmware_count = firmware_count;
1487    info->firmware       = firmware;
1488 }
1489 
core_info_get_config_file(const char * core_file_id,const char * info_dir)1490 static config_file_t *core_info_get_config_file(
1491       const char *core_file_id,
1492       const char *info_dir)
1493 {
1494    char info_path[PATH_MAX_LENGTH];
1495 
1496    if (string_is_empty(info_dir))
1497       snprintf(info_path, sizeof(info_path),
1498             "%s" ".info", core_file_id);
1499    else
1500    {
1501       info_path[0] = '\0';
1502       fill_pathname_join(info_path, info_dir, core_file_id,
1503             sizeof(info_path));
1504       strlcat(info_path, ".info", sizeof(info_path));
1505    }
1506 
1507    return config_file_new_from_path_to_string(info_path);
1508 }
1509 
core_info_parse_config_file(core_info_list_t * list,core_info_t * info,config_file_t * conf)1510 static void core_info_parse_config_file(
1511       core_info_list_t *list, core_info_t *info,
1512       config_file_t *conf)
1513 {
1514    struct config_entry_list *entry = NULL;
1515    bool tmp_bool                   = false;
1516 
1517    entry = config_get_entry(conf, "display_name");
1518 
1519    if (entry && !string_is_empty(entry->value))
1520    {
1521       info->display_name = entry->value;
1522       entry->value       = NULL;
1523    }
1524 
1525    entry = config_get_entry(conf, "display_version");
1526 
1527    if (entry && !string_is_empty(entry->value))
1528    {
1529       info->display_version = entry->value;
1530       entry->value          = NULL;
1531    }
1532 
1533    entry = config_get_entry(conf, "corename");
1534 
1535    if (entry && !string_is_empty(entry->value))
1536    {
1537       info->core_name = entry->value;
1538       entry->value    = NULL;
1539    }
1540 
1541    entry = config_get_entry(conf, "systemname");
1542 
1543    if (entry && !string_is_empty(entry->value))
1544    {
1545       info->systemname = entry->value;
1546       entry->value     = NULL;
1547    }
1548 
1549    entry = config_get_entry(conf, "systemid");
1550 
1551    if (entry && !string_is_empty(entry->value))
1552    {
1553       info->system_id = entry->value;
1554       entry->value    = NULL;
1555    }
1556 
1557    entry = config_get_entry(conf, "manufacturer");
1558 
1559    if (entry && !string_is_empty(entry->value))
1560    {
1561       info->system_manufacturer = entry->value;
1562       entry->value              = NULL;
1563    }
1564 
1565    entry = config_get_entry(conf, "supported_extensions");
1566 
1567    if (entry && !string_is_empty(entry->value))
1568    {
1569       info->supported_extensions      = entry->value;
1570       entry->value                    = NULL;
1571 
1572       info->supported_extensions_list =
1573             string_split(info->supported_extensions, "|");
1574    }
1575 
1576    entry = config_get_entry(conf, "authors");
1577 
1578    if (entry && !string_is_empty(entry->value))
1579    {
1580       info->authors      = entry->value;
1581       entry->value       = NULL;
1582 
1583       info->authors_list =
1584             string_split(info->authors, "|");
1585    }
1586 
1587    entry = config_get_entry(conf, "permissions");
1588 
1589    if (entry && !string_is_empty(entry->value))
1590    {
1591       info->permissions      = entry->value;
1592       entry->value           = NULL;
1593 
1594       info->permissions_list =
1595             string_split(info->permissions, "|");
1596    }
1597 
1598    entry = config_get_entry(conf, "license");
1599 
1600    if (entry && !string_is_empty(entry->value))
1601    {
1602       info->licenses      = entry->value;
1603       entry->value        = NULL;
1604 
1605       info->licenses_list =
1606             string_split(info->licenses, "|");
1607    }
1608 
1609    entry = config_get_entry(conf, "categories");
1610 
1611    if (entry && !string_is_empty(entry->value))
1612    {
1613       info->categories      = entry->value;
1614       entry->value          = NULL;
1615 
1616       info->categories_list =
1617             string_split(info->categories, "|");
1618    }
1619 
1620    entry = config_get_entry(conf, "database");
1621 
1622    if (entry && !string_is_empty(entry->value))
1623    {
1624       info->databases      = entry->value;
1625       entry->value         = NULL;
1626 
1627       info->databases_list =
1628             string_split(info->databases, "|");
1629    }
1630 
1631    entry = config_get_entry(conf, "notes");
1632 
1633    if (entry && !string_is_empty(entry->value))
1634    {
1635       info->notes     = entry->value;
1636       entry->value    = NULL;
1637 
1638       info->note_list =
1639             string_split(info->notes, "|");
1640    }
1641 
1642    entry = config_get_entry(conf, "required_hw_api");
1643 
1644    if (entry && !string_is_empty(entry->value))
1645    {
1646       info->required_hw_api      = entry->value;
1647       entry->value               = NULL;
1648 
1649       info->required_hw_api_list =
1650             string_split(info->required_hw_api, "|");
1651    }
1652 
1653    entry = config_get_entry(conf, "description");
1654 
1655    if (entry && !string_is_empty(entry->value))
1656    {
1657       info->description = entry->value;
1658       entry->value      = NULL;
1659    }
1660 
1661    if (config_get_bool(conf, "supports_no_game",
1662             &tmp_bool))
1663       info->supports_no_game = tmp_bool;
1664 
1665    if (config_get_bool(conf, "database_match_archive_member",
1666             &tmp_bool))
1667       info->database_match_archive_member = tmp_bool;
1668 
1669    if (config_get_bool(conf, "is_experimental",
1670             &tmp_bool))
1671       info->is_experimental = tmp_bool;
1672 
1673    core_info_resolve_firmware(info, conf);
1674 
1675    info->has_info = true;
1676    list->info_count++;
1677 }
1678 
core_info_list_resolve_all_extensions(core_info_list_t * core_info_list)1679 static void core_info_list_resolve_all_extensions(
1680       core_info_list_t *core_info_list)
1681 {
1682    size_t i              = 0;
1683    size_t all_ext_len    = 0;
1684    char *all_ext         = NULL;
1685 
1686    for (i = 0; i < core_info_list->count; i++)
1687    {
1688       if (core_info_list->list[i].supported_extensions)
1689          all_ext_len +=
1690             (strlen(core_info_list->list[i].supported_extensions) + 2);
1691    }
1692 
1693    all_ext_len += STRLEN_CONST("7z|") + STRLEN_CONST("zip|");
1694 
1695    all_ext      = (char*)calloc(1, all_ext_len);
1696 
1697    if (!all_ext)
1698       return;
1699 
1700    core_info_list->all_ext = all_ext;
1701 
1702    for (i = 0; i < core_info_list->count; i++)
1703    {
1704       if (!core_info_list->list[i].supported_extensions)
1705          continue;
1706 
1707       strlcat(core_info_list->all_ext,
1708             core_info_list->list[i].supported_extensions, all_ext_len);
1709       strlcat(core_info_list->all_ext, "|", all_ext_len);
1710    }
1711 #ifdef HAVE_7ZIP
1712    strlcat(core_info_list->all_ext, "7z|", all_ext_len);
1713 #endif
1714 #ifdef HAVE_ZLIB
1715    strlcat(core_info_list->all_ext, "zip|", all_ext_len);
1716 #endif
1717 }
1718 
core_info_free(core_info_t * info)1719 static void core_info_free(core_info_t* info)
1720 {
1721    size_t i;
1722 
1723    free(info->path);
1724    free(info->core_name);
1725    free(info->systemname);
1726    free(info->system_id);
1727    free(info->system_manufacturer);
1728    free(info->display_name);
1729    free(info->display_version);
1730    free(info->supported_extensions);
1731    free(info->authors);
1732    free(info->permissions);
1733    free(info->licenses);
1734    free(info->categories);
1735    free(info->databases);
1736    free(info->notes);
1737    free(info->required_hw_api);
1738    free(info->description);
1739    string_list_free(info->supported_extensions_list);
1740    string_list_free(info->authors_list);
1741    string_list_free(info->note_list);
1742    string_list_free(info->permissions_list);
1743    string_list_free(info->licenses_list);
1744    string_list_free(info->categories_list);
1745    string_list_free(info->databases_list);
1746    string_list_free(info->required_hw_api_list);
1747 
1748    for (i = 0; i < info->firmware_count; i++)
1749    {
1750       free(info->firmware[i].path);
1751       free(info->firmware[i].desc);
1752    }
1753    free(info->firmware);
1754 
1755    free(info->core_file_id.str);
1756 }
1757 
core_info_list_free(core_info_list_t * core_info_list)1758 static void core_info_list_free(core_info_list_t *core_info_list)
1759 {
1760    size_t i;
1761 
1762    if (!core_info_list)
1763       return;
1764 
1765    for (i = 0; i < core_info_list->count; i++)
1766    {
1767       core_info_t *info = (core_info_t*)&core_info_list->list[i];
1768       core_info_free(info);
1769    }
1770 
1771    free(core_info_list->all_ext);
1772    free(core_info_list->list);
1773    free(core_info_list);
1774 }
1775 
core_info_list_new(const char * path,const char * libretro_info_dir,const char * exts,bool dir_show_hidden_files,bool enable_cache,bool * cache_supported)1776 static core_info_list_t *core_info_list_new(const char *path,
1777       const char *libretro_info_dir,
1778       const char *exts,
1779       bool dir_show_hidden_files,
1780       bool enable_cache,
1781       bool *cache_supported)
1782 {
1783    size_t i;
1784    core_path_list_t *path_list                  = NULL;
1785    core_info_t *core_info                       = NULL;
1786    core_info_list_t *core_info_list             = NULL;
1787    core_info_cache_list_t *core_info_cache_list = NULL;
1788    const char *info_dir                         = libretro_info_dir;
1789 
1790    path_list = core_info_path_list_new(path, exts,
1791          dir_show_hidden_files);
1792    if (!path_list)
1793       goto error;
1794 
1795    core_info_list = (core_info_list_t*)malloc(sizeof(*core_info_list));
1796    if (!core_info_list)
1797       goto error;
1798 
1799    core_info_list->list       = NULL;
1800    core_info_list->count      = 0;
1801    core_info_list->info_count = 0;
1802    core_info_list->all_ext    = NULL;
1803 
1804    core_info = (core_info_t*)calloc(path_list->core_list->size,
1805          sizeof(*core_info));
1806 
1807    if (!core_info)
1808    {
1809       core_info_list_free(core_info_list);
1810       goto error;
1811    }
1812 
1813    core_info_list->list  = core_info;
1814    core_info_list->count = path_list->core_list->size;
1815 
1816 #if !defined(IOS)
1817    /* Read core info cache, if enabled
1818     * > This functionality is hard disabled on iOS/tvOS,
1819     *   where core path changes on every install
1820     *   (invalidating any cached parameters) */
1821    if (enable_cache)
1822    {
1823       core_info_cache_list = core_info_cache_read(info_dir);
1824       if (!core_info_cache_list)
1825          goto error;
1826    }
1827 #endif
1828 
1829    for (i = 0; i < path_list->core_list->size; i++)
1830    {
1831       core_info_t *info           = &core_info[i];
1832       core_file_path_t *core_file = &path_list->core_list->list[i];
1833       const char *base_path       = core_file->path;
1834       const char *core_filename   = core_file->filename;
1835       config_file_t *conf         = NULL;
1836       char core_file_id[256];
1837 
1838       core_file_id[0] = '\0';
1839 
1840       if (!core_info_get_file_id(core_filename, core_file_id,
1841                sizeof(core_file_id)))
1842          continue;
1843 
1844       /* If info cache is available, search for
1845        * current core */
1846       if (core_info_cache_list)
1847       {
1848          core_info_t *info_cache = core_info_cache_find(core_info_cache_list,
1849                core_file_id);
1850 
1851          if (info_cache)
1852          {
1853             core_info_copy(info_cache, info);
1854             /* Core lock status is 'dynamic', and
1855              * cannot be cached */
1856             info->is_locked = core_info_path_is_locked(path_list->lock_list,
1857                   core_filename);
1858             /* 'info_count' is normally incremented inside
1859              * core_info_parse_config_file(). If core entry
1860              * is cached, must instead increment the value
1861              * here */
1862             if (info->has_info)
1863                core_info_list->info_count++;
1864             continue;
1865          }
1866       }
1867 
1868       /* Cache core path */
1869       info->path = strdup(base_path);
1870 
1871       /* Get core lock status */
1872       info->is_locked = core_info_path_is_locked(path_list->lock_list,
1873             core_filename);
1874 
1875       /* Cache core file 'id' */
1876       info->core_file_id.str  = strdup(core_file_id);
1877       info->core_file_id.hash = core_info_hash_string(core_file_id);
1878 
1879       /* Parse core info file */
1880       conf = core_info_get_config_file(core_file_id, info_dir);
1881 
1882       if (conf)
1883       {
1884          core_info_parse_config_file(core_info_list, info, conf);
1885          config_file_free(conf);
1886       }
1887 
1888       /* Get fallback display name, if required */
1889       if (!info->display_name)
1890          info->display_name = strdup(core_filename);
1891 
1892       info->is_installed = true;
1893 
1894       /* If info cache is enabled and we reach this
1895        * point, current core is uncached
1896        * > Add it to the list, and trigger a cache
1897        *   refresh */
1898       if (core_info_cache_list)
1899       {
1900          core_info_cache_add(core_info_cache_list, info, false);
1901          core_info_cache_list->refresh = true;
1902       }
1903    }
1904 
1905    core_info_list_resolve_all_extensions(core_info_list);
1906 
1907    /* If info cache is enabled
1908     * > Check whether any cached cores have been
1909     *   uninstalled since the last run (triggers
1910     *   a refresh)
1911     * > Write new cache to disk if updates are
1912     *   required */
1913    *cache_supported = true;
1914    if (core_info_cache_list)
1915    {
1916       core_info_check_uninstalled(core_info_cache_list);
1917 
1918       if (core_info_cache_list->refresh)
1919          *cache_supported = core_info_cache_write(
1920                core_info_cache_list, info_dir);
1921 
1922       core_info_cache_list_free(core_info_cache_list);
1923    }
1924 
1925    core_info_path_list_free(path_list);
1926    return core_info_list;
1927 
1928 error:
1929    core_info_path_list_free(path_list);
1930    return NULL;
1931 }
1932 
1933 /* Shallow-copies internal state.
1934  *
1935  * Data in *info is invalidated when the
1936  * core_info_list is freed. */
core_info_list_get_info(core_info_list_t * core_info_list,core_info_t * out_info,const char * core_path)1937 bool core_info_list_get_info(core_info_list_t *core_info_list,
1938       core_info_t *out_info, const char *core_path)
1939 {
1940    core_info_t *info = core_info_find_internal(
1941          core_info_list, core_path);
1942 
1943    if (!out_info)
1944       return false;
1945 
1946    memset(out_info, 0, sizeof(*out_info));
1947 
1948    if (info)
1949    {
1950       *out_info = *info;
1951       return true;
1952    }
1953 
1954    return false;
1955 }
1956 
1957 #ifdef HAVE_COMPRESSION
core_info_does_support_any_file(const core_info_t * core,const struct string_list * list)1958 static bool core_info_does_support_any_file(const core_info_t *core,
1959       const struct string_list *list)
1960 {
1961    size_t i;
1962    if (!list || !core || !core->supported_extensions_list)
1963       return false;
1964 
1965    for (i = 0; i < list->size; i++)
1966       if (string_list_find_elem_prefix(core->supported_extensions_list,
1967                ".", path_get_extension(list->elems[i].data)))
1968          return true;
1969    return false;
1970 }
1971 #endif
1972 
core_info_does_support_file(const core_info_t * core,const char * path)1973 static bool core_info_does_support_file(
1974       const core_info_t *core, const char *path)
1975 {
1976    if (!core || !core->supported_extensions_list)
1977       return false;
1978    if (string_is_empty(path))
1979       return false;
1980 
1981    return string_list_find_elem_prefix(
1982          core->supported_extensions_list, ".", path_get_extension(path));
1983 }
1984 
1985 /* qsort_r() is not in standard C, sadly. */
1986 
core_info_qsort_cmp(const void * a_,const void * b_)1987 static int core_info_qsort_cmp(const void *a_, const void *b_)
1988 {
1989    core_info_state_t *p_coreinfo = coreinfo_get_ptr();
1990    const core_info_t          *a = (const core_info_t*)a_;
1991    const core_info_t          *b = (const core_info_t*)b_;
1992    int support_a                 = core_info_does_support_file(a,
1993          p_coreinfo->tmp_path);
1994    int support_b                 = core_info_does_support_file(b,
1995          p_coreinfo->tmp_path);
1996 #ifdef HAVE_COMPRESSION
1997    support_a            = support_a ||
1998       core_info_does_support_any_file(a, p_coreinfo->tmp_list);
1999    support_b            = support_b ||
2000       core_info_does_support_any_file(b, p_coreinfo->tmp_list);
2001 #endif
2002 
2003    if (support_a != support_b)
2004       return support_b - support_a;
2005    return strcasecmp(a->display_name, b->display_name);
2006 }
2007 
core_info_list_update_missing_firmware_internal(core_info_list_t * core_info_list,const char * core_path,const char * systemdir,bool * set_missing_bios)2008 static bool core_info_list_update_missing_firmware_internal(
2009       core_info_list_t *core_info_list,
2010       const char *core_path,
2011       const char *systemdir,
2012       bool *set_missing_bios)
2013 {
2014    size_t i;
2015    char path[PATH_MAX_LENGTH];
2016    core_info_t      *info = NULL;
2017 
2018    if (!core_info_list)
2019       return false;
2020 
2021    info                   = core_info_find_internal(core_info_list, core_path);
2022 
2023    if (!info)
2024       return false;
2025 
2026    path[0]                = '\0';
2027 
2028    for (i = 0; i < info->firmware_count; i++)
2029    {
2030       if (string_is_empty(info->firmware[i].path))
2031          continue;
2032 
2033       fill_pathname_join(path, systemdir,
2034             info->firmware[i].path, sizeof(path));
2035       info->firmware[i].missing = !path_is_valid(path);
2036       if (info->firmware[i].missing && !info->firmware[i].optional)
2037          *set_missing_bios = true;
2038    }
2039 
2040    return true;
2041 }
2042 
core_info_free_current_core(core_info_state_t * p_coreinfo)2043 void core_info_free_current_core(core_info_state_t *p_coreinfo)
2044 {
2045    if (p_coreinfo->current)
2046       free(p_coreinfo->current);
2047    p_coreinfo->current = NULL;
2048 }
2049 
core_info_init_current_core(void)2050 bool core_info_init_current_core(void)
2051 {
2052    core_info_state_t *p_coreinfo          = coreinfo_get_ptr();
2053    core_info_t *current                   = (core_info_t*)
2054       malloc(sizeof(*current));
2055    if (!current)
2056       return false;
2057    current->has_info                      = false;
2058    current->supports_no_game              = false;
2059    current->database_match_archive_member = false;
2060    current->is_experimental               = false;
2061    current->is_locked                     = false;
2062    current->firmware_count                = 0;
2063    current->path                          = NULL;
2064    current->display_name                  = NULL;
2065    current->display_version               = NULL;
2066    current->core_name                     = NULL;
2067    current->system_manufacturer           = NULL;
2068    current->systemname                    = NULL;
2069    current->system_id                     = NULL;
2070    current->supported_extensions          = NULL;
2071    current->authors                       = NULL;
2072    current->permissions                   = NULL;
2073    current->licenses                      = NULL;
2074    current->categories                    = NULL;
2075    current->databases                     = NULL;
2076    current->notes                         = NULL;
2077    current->required_hw_api               = NULL;
2078    current->description                   = NULL;
2079    current->categories_list               = NULL;
2080    current->databases_list                = NULL;
2081    current->note_list                     = NULL;
2082    current->supported_extensions_list     = NULL;
2083    current->authors_list                  = NULL;
2084    current->permissions_list              = NULL;
2085    current->licenses_list                 = NULL;
2086    current->required_hw_api_list          = NULL;
2087    current->firmware                      = NULL;
2088    current->core_file_id.str              = NULL;
2089    current->core_file_id.hash             = 0;
2090 
2091    p_coreinfo->current                    = current;
2092    return true;
2093 }
2094 
core_info_get_current_core(core_info_t ** core)2095 bool core_info_get_current_core(core_info_t **core)
2096 {
2097    core_info_state_t *p_coreinfo = coreinfo_get_ptr();
2098    if (!core)
2099       return false;
2100    *core = p_coreinfo->current;
2101    return true;
2102 }
2103 
core_info_deinit_list(void)2104 void core_info_deinit_list(void)
2105 {
2106    core_info_state_t *p_coreinfo = coreinfo_get_ptr();
2107    if (p_coreinfo->curr_list)
2108       core_info_list_free(p_coreinfo->curr_list);
2109    p_coreinfo->curr_list = NULL;
2110 }
2111 
core_info_init_list(const char * path_info,const char * dir_cores,const char * exts,bool dir_show_hidden_files,bool enable_cache,bool * cache_supported)2112 bool core_info_init_list(const char *path_info, const char *dir_cores,
2113       const char *exts, bool dir_show_hidden_files,
2114       bool enable_cache, bool *cache_supported)
2115 {
2116    core_info_state_t *p_coreinfo = coreinfo_get_ptr();
2117    if (!(p_coreinfo->curr_list = core_info_list_new(dir_cores,
2118                !string_is_empty(path_info) ? path_info : dir_cores,
2119                exts,
2120                dir_show_hidden_files,
2121                enable_cache,
2122                cache_supported)))
2123       return false;
2124    return true;
2125 }
2126 
core_info_get_list(core_info_list_t ** core)2127 bool core_info_get_list(core_info_list_t **core)
2128 {
2129    core_info_state_t *p_coreinfo = coreinfo_get_ptr();
2130    if (!core)
2131       return false;
2132    *core = p_coreinfo->curr_list;
2133    return true;
2134 }
2135 
2136 /* Returns number of installed cores */
core_info_count(void)2137 size_t core_info_count(void)
2138 {
2139    core_info_state_t *p_coreinfo = coreinfo_get_ptr();
2140    if (!p_coreinfo || !p_coreinfo->curr_list)
2141       return 0;
2142    return p_coreinfo->curr_list->count;
2143 }
2144 
core_info_list_update_missing_firmware(core_info_ctx_firmware_t * info,bool * set_missing_bios)2145 bool core_info_list_update_missing_firmware(core_info_ctx_firmware_t *info,
2146       bool *set_missing_bios)
2147 {
2148    core_info_state_t *p_coreinfo = coreinfo_get_ptr();
2149    if (!info)
2150       return false;
2151    return core_info_list_update_missing_firmware_internal(
2152          p_coreinfo->curr_list,
2153          info->path, info->directory.system,
2154          set_missing_bios);
2155 }
2156 
core_info_load(const char * core_path,core_info_state_t * p_coreinfo)2157 bool core_info_load(const char *core_path,
2158       core_info_state_t *p_coreinfo)
2159 {
2160    core_info_t    *core_info     = NULL;
2161 
2162    if (!p_coreinfo->current)
2163       core_info_init_current_core();
2164 
2165    core_info_get_current_core(&core_info);
2166 
2167    if (!p_coreinfo->curr_list)
2168       return false;
2169 
2170    if (!core_info_list_get_info(p_coreinfo->curr_list,
2171             core_info, core_path))
2172       return false;
2173 
2174    return true;
2175 }
2176 
core_info_find(const char * core_path,core_info_t ** core_info)2177 bool core_info_find(const char *core_path,
2178       core_info_t **core_info)
2179 {
2180    core_info_state_t *p_coreinfo = coreinfo_get_ptr();
2181    core_info_t *info             = NULL;
2182 
2183    if (!core_info || !p_coreinfo->curr_list)
2184       return false;
2185 
2186    info = core_info_find_internal(p_coreinfo->curr_list, core_path);
2187 
2188    if (!info)
2189       return false;
2190 
2191    *core_info = info;
2192    return true;
2193 }
2194 
core_info_get(core_info_list_t * list,size_t i)2195 core_info_t *core_info_get(core_info_list_t *list, size_t i)
2196 {
2197    core_info_t *info = NULL;
2198 
2199    if (!list || (i >= list->count))
2200       return NULL;
2201    info = (core_info_t*)&list->list[i];
2202    if (!info || !info->path)
2203       return NULL;
2204 
2205    return info;
2206 }
2207 
core_info_list_get_supported_cores(core_info_list_t * core_info_list,const char * path,const core_info_t ** infos,size_t * num_infos)2208 void core_info_list_get_supported_cores(core_info_list_t *core_info_list,
2209       const char *path, const core_info_t **infos, size_t *num_infos)
2210 {
2211    size_t i;
2212    size_t supported              = 0;
2213 #ifdef HAVE_COMPRESSION
2214    struct string_list *list      = NULL;
2215 #endif
2216    core_info_state_t *p_coreinfo = coreinfo_get_ptr();
2217 
2218    if (!core_info_list)
2219       return;
2220 
2221    p_coreinfo->tmp_path          = path;
2222 
2223 #ifdef HAVE_COMPRESSION
2224    if (path_is_compressed_file(path))
2225       list = file_archive_get_file_list(path, NULL);
2226    p_coreinfo->tmp_list = list;
2227 #endif
2228 
2229    /* Let supported core come first in list so we can return
2230     * a pointer to them. */
2231    qsort(core_info_list->list, core_info_list->count,
2232          sizeof(core_info_t), core_info_qsort_cmp);
2233 
2234    for (i = 0; i < core_info_list->count; i++, supported++)
2235    {
2236       const core_info_t *core = &core_info_list->list[i];
2237 
2238       if (core_info_does_support_file(core, path))
2239          continue;
2240 
2241 #ifdef HAVE_COMPRESSION
2242       if (core_info_does_support_any_file(core, list))
2243          continue;
2244 #endif
2245 
2246       break;
2247    }
2248 
2249 #ifdef HAVE_COMPRESSION
2250    if (list)
2251       string_list_free(list);
2252 #endif
2253 
2254    *infos     = core_info_list->list;
2255    *num_infos = supported;
2256 }
2257 
2258 /*
2259  * Matches core A and B file IDs
2260  *
2261  * e.g.:
2262  *   snes9x_libretro.dll and snes9x_libretro_android.so are matched
2263  *   snes9x__2005_libretro.dll and snes9x_libretro_android.so are
2264  *   NOT matched
2265  */
core_info_core_file_id_is_equal(const char * core_path_a,const char * core_path_b)2266 bool core_info_core_file_id_is_equal(const char *core_path_a,
2267       const char *core_path_b)
2268 {
2269    char core_file_id_a[256];
2270    char core_file_id_b[256];
2271 
2272    core_file_id_a[0] = '\0';
2273    core_file_id_b[0] = '\0';
2274 
2275    if (string_is_empty(core_path_a) ||
2276        string_is_empty(core_path_b) ||
2277        !core_info_get_file_id(path_basename_nocompression(core_path_a),
2278             core_file_id_a, sizeof(core_file_id_a)) ||
2279        !core_info_get_file_id(path_basename_nocompression(core_path_b),
2280             core_file_id_b, sizeof(core_file_id_b)))
2281       return false;
2282 
2283    return string_is_equal(core_file_id_a, core_file_id_b);
2284 }
2285 
core_info_database_match_archive_member(const char * database_path)2286 bool core_info_database_match_archive_member(const char *database_path)
2287 {
2288    char      *database           = NULL;
2289    const char      *new_path     = path_basename_nocompression(database_path);
2290    core_info_state_t *p_coreinfo = NULL;
2291 
2292    if (string_is_empty(new_path))
2293       return false;
2294    if (!(database = strdup(new_path)))
2295       return false;
2296 
2297    path_remove_extension(database);
2298 
2299    p_coreinfo               = coreinfo_get_ptr();
2300 
2301    if (p_coreinfo->curr_list)
2302    {
2303       size_t i;
2304 
2305       for (i = 0; i < p_coreinfo->curr_list->count; i++)
2306       {
2307          const core_info_t *info = &p_coreinfo->curr_list->list[i];
2308 
2309          if (!info->database_match_archive_member)
2310              continue;
2311 
2312          if (!string_list_find_elem(info->databases_list, database))
2313              continue;
2314 
2315          free(database);
2316          return true;
2317       }
2318    }
2319 
2320    free(database);
2321    return false;
2322 }
2323 
core_info_database_supports_content_path(const char * database_path,const char * path)2324 bool core_info_database_supports_content_path(
2325       const char *database_path, const char *path)
2326 {
2327    char      *database           = NULL;
2328    const char      *new_path     = path_basename(database_path);
2329    core_info_state_t *p_coreinfo = NULL;
2330 
2331    if (string_is_empty(new_path))
2332       return false;
2333    if (!(database = strdup(new_path)))
2334       return false;
2335 
2336    path_remove_extension(database);
2337 
2338    p_coreinfo                    = coreinfo_get_ptr();
2339 
2340    if (p_coreinfo->curr_list)
2341    {
2342       size_t i;
2343 
2344       for (i = 0; i < p_coreinfo->curr_list->count; i++)
2345       {
2346          const core_info_t *info = &p_coreinfo->curr_list->list[i];
2347 
2348          if (!string_list_find_elem(info->supported_extensions_list,
2349                   path_get_extension(path)))
2350             continue;
2351 
2352          if (!string_list_find_elem(info->databases_list, database))
2353             continue;
2354 
2355          free(database);
2356          return true;
2357       }
2358    }
2359 
2360    free(database);
2361    return false;
2362 }
2363 
core_info_list_get_display_name(core_info_list_t * core_info_list,const char * core_path,char * s,size_t len)2364 bool core_info_list_get_display_name(core_info_list_t *core_info_list,
2365       const char *core_path, char *s, size_t len)
2366 {
2367    core_info_t *info = core_info_find_internal(
2368          core_info_list, core_path);
2369 
2370    if (s &&
2371        info &&
2372        !string_is_empty(info->display_name))
2373    {
2374       strlcpy(s, info->display_name, len);
2375       return true;
2376    }
2377 
2378    return false;
2379 }
2380 
2381 /* Returns core_info parameters required for
2382  * core updater tasks, read from specified file.
2383  * Returned core_updater_info_t object must be
2384  * freed using core_info_free_core_updater_info().
2385  * Returns NULL if 'path' is invalid. */
core_info_get_core_updater_info(const char * info_path)2386 core_updater_info_t *core_info_get_core_updater_info(const char *info_path)
2387 {
2388    struct config_entry_list
2389       *entry                 = NULL;
2390    bool tmp_bool             = false;
2391    core_updater_info_t *info = NULL;
2392    config_file_t *conf       = NULL;
2393 
2394    if (string_is_empty(info_path))
2395       return NULL;
2396 
2397    /* Read config file */
2398    conf = config_file_new_from_path_to_string(info_path);
2399 
2400    if (!conf)
2401       return NULL;
2402 
2403    /* Create info struct */
2404    info                      = (core_updater_info_t*)malloc(sizeof(*info));
2405 
2406    if (!info)
2407       return NULL;
2408 
2409    info->is_experimental     = false;
2410    info->display_name        = NULL;
2411    info->description         = NULL;
2412    info->licenses            = NULL;
2413 
2414    /* Fetch required parameters */
2415 
2416    /* > is_experimental */
2417    if (config_get_bool(conf, "is_experimental", &tmp_bool))
2418       info->is_experimental  = tmp_bool;
2419 
2420    /* > display_name */
2421    entry                     = config_get_entry(conf, "display_name");
2422 
2423    if (entry && !string_is_empty(entry->value))
2424    {
2425       info->display_name     = entry->value;
2426       entry->value           = NULL;
2427    }
2428 
2429    /* > description */
2430    entry                     = config_get_entry(conf, "description");
2431 
2432    if (entry && !string_is_empty(entry->value))
2433    {
2434       info->description      = entry->value;
2435       entry->value           = NULL;
2436    }
2437 
2438    /* > licenses */
2439    entry                     = config_get_entry(conf, "license");
2440 
2441    if (entry && !string_is_empty(entry->value))
2442    {
2443       info->licenses         = entry->value;
2444       entry->value           = NULL;
2445    }
2446 
2447    /* Clean up */
2448    config_file_free(conf);
2449 
2450    return info;
2451 }
2452 
core_info_free_core_updater_info(core_updater_info_t * info)2453 void core_info_free_core_updater_info(core_updater_info_t *info)
2454 {
2455    if (!info)
2456       return;
2457 
2458    if (info->display_name)
2459       free(info->display_name);
2460 
2461    if (info->description)
2462       free(info->description);
2463 
2464    if (info->licenses)
2465       free(info->licenses);
2466 
2467    free(info);
2468    info = NULL;
2469 }
2470 
core_info_qsort_func_path(const core_info_t * a,const core_info_t * b)2471 static int core_info_qsort_func_path(const core_info_t *a,
2472       const core_info_t *b)
2473 {
2474    if (!a || !b)
2475       return 0;
2476 
2477    if (string_is_empty(a->path) || string_is_empty(b->path))
2478       return 0;
2479 
2480    return strcasecmp(a->path, b->path);
2481 }
2482 
core_info_qsort_func_display_name(const core_info_t * a,const core_info_t * b)2483 static int core_info_qsort_func_display_name(const core_info_t *a,
2484       const core_info_t *b)
2485 {
2486    if (!a || !b)
2487       return 0;
2488 
2489    if (string_is_empty(a->display_name) || string_is_empty(b->display_name))
2490       return 0;
2491 
2492    return strcasecmp(a->display_name, b->display_name);
2493 }
2494 
core_info_qsort_func_core_name(const core_info_t * a,const core_info_t * b)2495 static int core_info_qsort_func_core_name(const core_info_t *a,
2496       const core_info_t *b)
2497 {
2498    if (!a || !b)
2499       return 0;
2500 
2501    if (string_is_empty(a->core_name) || string_is_empty(b->core_name))
2502       return 0;
2503 
2504    return strcasecmp(a->core_name, b->core_name);
2505 }
2506 
core_info_qsort_func_system_name(const core_info_t * a,const core_info_t * b)2507 static int core_info_qsort_func_system_name(const core_info_t *a,
2508       const core_info_t *b)
2509 {
2510    if (!a || !b)
2511       return 0;
2512 
2513    if (string_is_empty(a->systemname) || string_is_empty(b->systemname))
2514       return 0;
2515 
2516    return strcasecmp(a->systemname, b->systemname);
2517 }
2518 
core_info_qsort(core_info_list_t * core_info_list,enum core_info_list_qsort_type qsort_type)2519 void core_info_qsort(core_info_list_t *core_info_list,
2520       enum core_info_list_qsort_type qsort_type)
2521 {
2522    if (!core_info_list)
2523       return;
2524 
2525    if (core_info_list->count < 2)
2526       return;
2527 
2528    switch (qsort_type)
2529    {
2530       case CORE_INFO_LIST_SORT_PATH:
2531          qsort(core_info_list->list,
2532                core_info_list->count,
2533                sizeof(core_info_t),
2534                (int (*)(const void *, const void *))
2535                core_info_qsort_func_path);
2536          break;
2537       case CORE_INFO_LIST_SORT_DISPLAY_NAME:
2538          qsort(core_info_list->list,
2539                core_info_list->count,
2540                sizeof(core_info_t),
2541                (int (*)(const void *, const void *))
2542                core_info_qsort_func_display_name);
2543          break;
2544       case CORE_INFO_LIST_SORT_CORE_NAME:
2545          qsort(core_info_list->list,
2546                core_info_list->count,
2547                sizeof(core_info_t),
2548                (int (*)(const void *, const void *))
2549                core_info_qsort_func_core_name);
2550          break;
2551       case CORE_INFO_LIST_SORT_SYSTEM_NAME:
2552          qsort(core_info_list->list,
2553                core_info_list->count,
2554                sizeof(core_info_t),
2555                (int (*)(const void *, const void *))
2556                core_info_qsort_func_system_name);
2557          break;
2558       default:
2559          return;
2560    }
2561 }
2562 
core_info_compare_api_version(int sys_major,int sys_minor,int major,int minor,enum compare_op op)2563 static bool core_info_compare_api_version(int sys_major, int sys_minor, int major, int minor, enum compare_op op)
2564 {
2565    switch (op)
2566    {
2567       case COMPARE_OP_EQUAL:
2568          if (sys_major == major && sys_minor == minor)
2569             return true;
2570          break;
2571       case COMPARE_OP_NOT_EQUAL:
2572          if (!(sys_major == major && sys_minor == minor))
2573             return true;
2574          break;
2575       case COMPARE_OP_LESS:
2576          if (sys_major < major || (sys_major == major && sys_minor < minor))
2577             return true;
2578          break;
2579       case COMPARE_OP_LESS_EQUAL:
2580          if (sys_major < major || (sys_major == major && sys_minor <= minor))
2581             return true;
2582          break;
2583       case COMPARE_OP_GREATER:
2584          if (sys_major > major || (sys_major == major && sys_minor > minor))
2585             return true;
2586          break;
2587       case COMPARE_OP_GREATER_EQUAL:
2588          if (sys_major > major || (sys_major == major && sys_minor >= minor))
2589             return true;
2590          break;
2591       default:
2592          break;
2593    }
2594 
2595    return false;
2596 }
2597 
core_info_hw_api_supported(core_info_t * info)2598 bool core_info_hw_api_supported(core_info_t *info)
2599 {
2600 #ifdef RARCH_INTERNAL
2601    unsigned i;
2602    enum gfx_ctx_api sys_api;
2603    int sys_api_version_major       = 0;
2604    int sys_api_version_minor       = 0;
2605    const char *sys_api_version_str = video_driver_get_gpu_api_version_string();
2606    gfx_ctx_flags_t sys_flags       = video_driver_get_flags_wrapper();
2607 
2608    enum api_parse_state
2609    {
2610       STATE_API_NAME,
2611       STATE_API_COMPARE_OP,
2612       STATE_API_VERSION
2613    };
2614 
2615    if (!info || !info->required_hw_api_list || info->required_hw_api_list->size == 0)
2616       return true;
2617 
2618    sys_api = video_context_driver_get_api();
2619 
2620    for (i = 0; i < info->required_hw_api_list->size; i++)
2621    {
2622       char api_str[32]           = {0};
2623       char version[16]           = {0};
2624       char major_str[16]         = {0};
2625       char minor_str[16]         = {0};
2626       const char *cur_api        = info->required_hw_api_list->elems[i].data;
2627       int api_pos                = 0;
2628       int major_str_pos          = 0;
2629       int minor_str_pos          = 0;
2630       int major                  = 0;
2631       int minor                  = 0;
2632       unsigned cur_api_len       = 0;
2633       unsigned j                 = 0;
2634       bool found_major           = false;
2635       bool found_minor           = false;
2636       enum compare_op op         = COMPARE_OP_GREATER_EQUAL;
2637       enum api_parse_state state = STATE_API_NAME;
2638 
2639       if (string_is_empty(cur_api))
2640          continue;
2641 
2642       cur_api_len                = (int)strlen(cur_api);
2643 
2644       for (j = 0; j < cur_api_len; j++)
2645       {
2646          if (cur_api[j] == ' ')
2647             continue;
2648 
2649          switch (state)
2650          {
2651             case STATE_API_NAME:
2652             {
2653                if (  ISUPPER((unsigned char)cur_api[j]) ||
2654                      ISLOWER((unsigned char)cur_api[j]))
2655                   api_str[api_pos++] = cur_api[j];
2656                else
2657                {
2658                   j--;
2659                   state = STATE_API_COMPARE_OP;
2660                   break;
2661                }
2662 
2663                break;
2664             }
2665             case STATE_API_COMPARE_OP:
2666             {
2667                if (j < cur_api_len - 1 && !(cur_api[j] >= '0' && cur_api[j] <= '9'))
2668                {
2669                   if (cur_api[j] == '=' && cur_api[j + 1] == '=')
2670                   {
2671                      op = COMPARE_OP_EQUAL;
2672                      j++;
2673                   }
2674                   else if (cur_api[j] == '=')
2675                      op = COMPARE_OP_EQUAL;
2676                   else if (cur_api[j] == '!' && cur_api[j + 1] == '=')
2677                   {
2678                      op = COMPARE_OP_NOT_EQUAL;
2679                      j++;
2680                   }
2681                   else if (cur_api[j] == '<' && cur_api[j + 1] == '=')
2682                   {
2683                      op = COMPARE_OP_LESS_EQUAL;
2684                      j++;
2685                   }
2686                   else if (cur_api[j] == '>' && cur_api[j + 1] == '=')
2687                   {
2688                      op = COMPARE_OP_GREATER_EQUAL;
2689                      j++;
2690                   }
2691                   else if (cur_api[j] == '<')
2692                      op = COMPARE_OP_LESS;
2693                   else if (cur_api[j] == '>')
2694                      op = COMPARE_OP_GREATER;
2695                }
2696 
2697                state = STATE_API_VERSION;
2698 
2699                break;
2700             }
2701             case STATE_API_VERSION:
2702             {
2703                if (    !found_minor
2704                      && cur_api[j] >= '0'
2705                      && cur_api[j] <= '9'
2706                      && cur_api[j] != '.')
2707                {
2708                   found_major = true;
2709 
2710                   if (major_str_pos < sizeof(major_str) - 1)
2711                      major_str[major_str_pos++] = cur_api[j];
2712                }
2713                else if (
2714                         found_major
2715                      && found_minor
2716                      && cur_api[j] >= '0'
2717                      && cur_api[j] <= '9')
2718                {
2719                   if (minor_str_pos < sizeof(minor_str) - 1)
2720                      minor_str[minor_str_pos++] = cur_api[j];
2721                }
2722                else if (cur_api[j] == '.')
2723                   found_minor = true;
2724                break;
2725             }
2726             default:
2727                break;
2728          }
2729       }
2730 
2731       sscanf(major_str, "%d", &major);
2732       sscanf(minor_str, "%d", &minor);
2733       snprintf(version, sizeof(version), "%d.%d", major, minor);
2734 #if 0
2735       printf("Major: %d\n", major);
2736       printf("Minor: %d\n", minor);
2737       printf("API: %s\n", api_str);
2738       printf("Version: %s\n", version);
2739       fflush(stdout);
2740 #endif
2741 
2742       if ((string_is_equal_noncase(api_str, "opengl") && sys_api == GFX_CTX_OPENGL_API) ||
2743             (string_is_equal_noncase(api_str, "openglcompat") && sys_api == GFX_CTX_OPENGL_API) ||
2744             (string_is_equal_noncase(api_str, "openglcompatibility") && sys_api == GFX_CTX_OPENGL_API))
2745       {
2746          /* system is running a core context while compat is requested */
2747          if (sys_flags.flags & (1 << GFX_CTX_FLAGS_GL_CORE_CONTEXT))
2748             return false;
2749 
2750          sscanf(sys_api_version_str, "%d.%d", &sys_api_version_major, &sys_api_version_minor);
2751 
2752          if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
2753             return true;
2754       }
2755       else if (string_is_equal_noncase(api_str, "openglcore") && sys_api == GFX_CTX_OPENGL_API)
2756       {
2757          sscanf(sys_api_version_str, "%d.%d", &sys_api_version_major, &sys_api_version_minor);
2758 
2759          if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
2760             return true;
2761       }
2762       else if (string_is_equal_noncase(api_str, "opengles") && sys_api == GFX_CTX_OPENGL_ES_API)
2763       {
2764          sscanf(sys_api_version_str, "OpenGL ES %d.%d", &sys_api_version_major, &sys_api_version_minor);
2765 
2766          if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
2767             return true;
2768       }
2769       else if (string_is_equal_noncase(api_str, "direct3d8") && sys_api == GFX_CTX_DIRECT3D8_API)
2770       {
2771          sys_api_version_major = 8;
2772          sys_api_version_minor = 0;
2773 
2774          if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
2775             return true;
2776       }
2777       else if (string_is_equal_noncase(api_str, "direct3d9") && sys_api == GFX_CTX_DIRECT3D9_API)
2778       {
2779          sys_api_version_major = 9;
2780          sys_api_version_minor = 0;
2781 
2782          if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
2783             return true;
2784       }
2785       else if (string_is_equal_noncase(api_str, "direct3d10") && sys_api == GFX_CTX_DIRECT3D10_API)
2786       {
2787          sys_api_version_major = 10;
2788          sys_api_version_minor = 0;
2789 
2790          if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
2791             return true;
2792       }
2793       else if (string_is_equal_noncase(api_str, "direct3d11") && sys_api == GFX_CTX_DIRECT3D11_API)
2794       {
2795          sys_api_version_major = 11;
2796          sys_api_version_minor = 0;
2797 
2798          if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
2799             return true;
2800       }
2801       else if (string_is_equal_noncase(api_str, "direct3d12") && sys_api == GFX_CTX_DIRECT3D12_API)
2802       {
2803          sys_api_version_major = 12;
2804          sys_api_version_minor = 0;
2805 
2806          if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
2807             return true;
2808       }
2809       else if (string_is_equal_noncase(api_str, "vulkan") && sys_api == GFX_CTX_VULKAN_API)
2810       {
2811          sscanf(sys_api_version_str, "%d.%d", &sys_api_version_major, &sys_api_version_minor);
2812 
2813          if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
2814             return true;
2815       }
2816       else if (string_is_equal_noncase(api_str, "metal") && sys_api == GFX_CTX_METAL_API)
2817       {
2818          sscanf(sys_api_version_str, "%d.%d", &sys_api_version_major, &sys_api_version_minor);
2819 
2820          if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
2821             return true;
2822       }
2823    }
2824 
2825    return false;
2826 #else
2827    return true;
2828 #endif
2829 }
2830 
2831 /* Sets 'locked' status of specified core
2832  * > Returns true if successful
2833  * > Like all functions that access the cached
2834  *   core info list this is *not* thread safe */
core_info_set_core_lock(const char * core_path,bool lock)2835 bool core_info_set_core_lock(const char *core_path, bool lock)
2836 {
2837    core_info_t *core_info = NULL;
2838    bool lock_file_exists  = false;
2839    char lock_file_path[PATH_MAX_LENGTH];
2840 
2841 #if defined(ANDROID)
2842    /* Play Store builds do not support
2843     * core locking */
2844    if (play_feature_delivery_enabled())
2845       return false;
2846 #endif
2847 
2848    if (string_is_empty(core_path))
2849       return false;
2850 
2851    /* Search for specified core */
2852    if (!core_info_find(core_path, &core_info))
2853       return false;
2854 
2855    if (string_is_empty(core_info->path))
2856       return false;
2857 
2858    /* Get lock file path */
2859    snprintf(lock_file_path, sizeof(lock_file_path),
2860          "%s" FILE_PATH_LOCK_EXTENSION, core_info->path);
2861 
2862    /* Check whether lock file exists */
2863    lock_file_exists = path_is_valid(lock_file_path);
2864 
2865    /* Create or delete lock file, as required */
2866    if (lock && !lock_file_exists)
2867    {
2868       RFILE *lock_file = filestream_open(
2869             lock_file_path,
2870             RETRO_VFS_FILE_ACCESS_WRITE,
2871             RETRO_VFS_FILE_ACCESS_HINT_NONE);
2872 
2873       if (!lock_file)
2874          return false;
2875 
2876       /* We have to write something - just output
2877        * a single character */
2878       if (filestream_putc(lock_file, 0) != 0)
2879       {
2880          filestream_close(lock_file);
2881          return false;
2882       }
2883 
2884       filestream_close(lock_file);
2885    }
2886    else if (!lock && lock_file_exists)
2887       if (filestream_delete(lock_file_path) != 0)
2888          return false;
2889 
2890    /* File operations were successful - update
2891     * core info entry */
2892    core_info->is_locked = lock;
2893 
2894    return true;
2895 }
2896 
2897 /* Fetches 'locked' status of specified core
2898  * > If 'validate_path' is 'true', will search
2899  *   cached core info list for a corresponding
2900  *   'sanitised' core file path. This is *not*
2901  *   thread safe
2902  * > If 'validate_path' is 'false', performs a
2903  *   direct filesystem check. This *is* thread
2904  *   safe, but validity of specified core path
2905  *   must be checked externally */
core_info_get_core_lock(const char * core_path,bool validate_path)2906 bool core_info_get_core_lock(const char *core_path, bool validate_path)
2907 {
2908    core_info_t *core_info     = NULL;
2909    const char *core_file_path = NULL;
2910    bool is_locked             = false;
2911    char lock_file_path[PATH_MAX_LENGTH];
2912 
2913 #if defined(ANDROID)
2914    /* Play Store builds do not support
2915     * core locking */
2916    if (play_feature_delivery_enabled())
2917       return false;
2918 #endif
2919 
2920    if (string_is_empty(core_path))
2921       return false;
2922 
2923    /* Check whether core path is to be validated */
2924    if (validate_path)
2925    {
2926       if (core_info_find(core_path, &core_info))
2927          core_file_path = core_info->path;
2928    }
2929    else
2930       core_file_path = core_path;
2931 
2932    /* A core cannot be locked if it does not exist... */
2933    if (string_is_empty(core_file_path) ||
2934        !path_is_valid(core_file_path))
2935       return false;
2936 
2937    /* Get lock file path */
2938    snprintf(lock_file_path, sizeof(lock_file_path),
2939          "%s" FILE_PATH_LOCK_EXTENSION, core_file_path);
2940 
2941    /* Check whether lock file exists */
2942    is_locked = path_is_valid(lock_file_path);
2943 
2944    /* If core path has been validated (and a
2945     * core info object is available), ensure
2946     * that core info 'is_locked' field is
2947     * up to date */
2948    if (validate_path && core_info)
2949       core_info->is_locked = is_locked;
2950 
2951    return is_locked;
2952 }
2953