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