1 /* Copyright (C) 2010-2019 The RetroArch team
2 *
3 * ---------------------------------------------------------------------------------------
4 * The following license statement only applies to this file (core_updater_list.c).
5 * ---------------------------------------------------------------------------------------
6 *
7 * Permission is hereby granted, free of charge,
8 * to any person obtaining a copy of this software and associated documentation files (the "Software"),
9 * to deal in the Software without restriction, including without limitation the rights to
10 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
11 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 */
22
23 #include <file/file_path.h>
24 #include <string/stdstring.h>
25 #include <lists/string_list.h>
26 #include <net/net_http.h>
27 #include <array/rbuf.h>
28 #include <retro_miscellaneous.h>
29
30 #include "file_path_special.h"
31 #include "core_info.h"
32
33 #include "core_updater_list.h"
34
35 /* Holds all entries in a core updater list */
36 struct core_updater_list
37 {
38 core_updater_list_entry_t *entries;
39 enum core_updater_list_type type;
40 };
41
42 /* Cached ('global') core updater list */
43 static core_updater_list_t *core_list_cached = NULL;
44
45 /**************************************/
46 /* Initialisation / De-Initialisation */
47 /**************************************/
48
49 /* Frees contents of specified core updater
50 * list entry */
core_updater_list_free_entry(core_updater_list_entry_t * entry)51 static void core_updater_list_free_entry(core_updater_list_entry_t *entry)
52 {
53 if (!entry)
54 return;
55
56 if (entry->remote_filename)
57 {
58 free(entry->remote_filename);
59 entry->remote_filename = NULL;
60 }
61
62 if (entry->remote_core_path)
63 {
64 free(entry->remote_core_path);
65 entry->remote_core_path = NULL;
66 }
67
68 if (entry->local_core_path)
69 {
70 free(entry->local_core_path);
71 entry->local_core_path = NULL;
72 }
73
74 if (entry->local_info_path)
75 {
76 free(entry->local_info_path);
77 entry->local_info_path = NULL;
78 }
79
80 if (entry->display_name)
81 {
82 free(entry->display_name);
83 entry->display_name = NULL;
84 }
85
86 if (entry->description)
87 {
88 free(entry->description);
89 entry->description = NULL;
90 }
91
92 if (entry->licenses_list)
93 {
94 string_list_free(entry->licenses_list);
95 entry->licenses_list = NULL;
96 }
97 }
98
99 /* Creates a new, empty core updater list.
100 * Returns a handle to a new core_updater_list_t object
101 * on success, otherwise returns NULL. */
core_updater_list_init(void)102 core_updater_list_t *core_updater_list_init(void)
103 {
104 /* Create core updater list */
105 core_updater_list_t *core_list = (core_updater_list_t*)
106 malloc(sizeof(*core_list));
107
108 if (!core_list)
109 return NULL;
110
111 /* Initialise members */
112 core_list->entries = NULL;
113 core_list->type = CORE_UPDATER_LIST_TYPE_UNKNOWN;
114
115 return core_list;
116 }
117
118 /* Resets (removes all entries of) specified core
119 * updater list */
core_updater_list_reset(core_updater_list_t * core_list)120 void core_updater_list_reset(core_updater_list_t *core_list)
121 {
122 if (!core_list)
123 return;
124
125 if (core_list->entries)
126 {
127 size_t i;
128
129 for (i = 0; i < RBUF_LEN(core_list->entries); i++)
130 core_updater_list_free_entry(&core_list->entries[i]);
131
132 RBUF_FREE(core_list->entries);
133 }
134
135 core_list->type = CORE_UPDATER_LIST_TYPE_UNKNOWN;
136 }
137
138 /* Frees specified core updater list */
core_updater_list_free(core_updater_list_t * core_list)139 void core_updater_list_free(core_updater_list_t *core_list)
140 {
141 if (!core_list)
142 return;
143
144 core_updater_list_reset(core_list);
145 free(core_list);
146 }
147
148 /***************/
149 /* Cached List */
150 /***************/
151
152 /* Creates a new, empty cached core updater list
153 * (i.e. 'global' list).
154 * Returns false in the event of an error. */
core_updater_list_init_cached(void)155 bool core_updater_list_init_cached(void)
156 {
157 /* Free any existing cached core updater list */
158 if (core_list_cached)
159 {
160 core_updater_list_free(core_list_cached);
161 core_list_cached = NULL;
162 }
163
164 core_list_cached = core_updater_list_init();
165
166 if (!core_list_cached)
167 return false;
168
169 return true;
170 }
171
172 /* Fetches cached core updater list */
core_updater_list_get_cached(void)173 core_updater_list_t *core_updater_list_get_cached(void)
174 {
175 if (core_list_cached)
176 return core_list_cached;
177
178 return NULL;
179 }
180
181 /* Frees cached core updater list */
core_updater_list_free_cached(void)182 void core_updater_list_free_cached(void)
183 {
184 core_updater_list_free(core_list_cached);
185 core_list_cached = NULL;
186 }
187
188 /***********/
189 /* Getters */
190 /***********/
191
192 /* Returns number of entries in core updater list */
core_updater_list_size(core_updater_list_t * core_list)193 size_t core_updater_list_size(core_updater_list_t *core_list)
194 {
195 if (!core_list)
196 return 0;
197
198 return RBUF_LEN(core_list->entries);
199 }
200
201 /* Returns 'type' (core delivery method) of
202 * specified core updater list */
core_updater_list_get_type(core_updater_list_t * core_list)203 enum core_updater_list_type core_updater_list_get_type(
204 core_updater_list_t *core_list)
205 {
206 if (!core_list)
207 return CORE_UPDATER_LIST_TYPE_UNKNOWN;
208
209 return core_list->type;
210 }
211
212 /* Fetches core updater list entry corresponding
213 * to the specified entry index.
214 * Returns false if index is invalid. */
core_updater_list_get_index(core_updater_list_t * core_list,size_t idx,const core_updater_list_entry_t ** entry)215 bool core_updater_list_get_index(
216 core_updater_list_t *core_list,
217 size_t idx,
218 const core_updater_list_entry_t **entry)
219 {
220 if (!core_list || !entry)
221 return false;
222
223 if (idx >= RBUF_LEN(core_list->entries))
224 return false;
225
226 *entry = &core_list->entries[idx];
227
228 return true;
229 }
230
231 /* Fetches core updater list entry corresponding
232 * to the specified remote core filename.
233 * Returns false if core is not found. */
core_updater_list_get_filename(core_updater_list_t * core_list,const char * remote_filename,const core_updater_list_entry_t ** entry)234 bool core_updater_list_get_filename(
235 core_updater_list_t *core_list,
236 const char *remote_filename,
237 const core_updater_list_entry_t **entry)
238 {
239 size_t num_entries;
240 size_t i;
241
242 if (!core_list || !entry || string_is_empty(remote_filename))
243 return false;
244
245 num_entries = RBUF_LEN(core_list->entries);
246
247 if (num_entries < 1)
248 return false;
249
250 /* Search for specified filename */
251 for (i = 0; i < num_entries; i++)
252 {
253 core_updater_list_entry_t *current_entry = &core_list->entries[i];
254
255 if (string_is_empty(current_entry->remote_filename))
256 continue;
257
258 if (string_is_equal(remote_filename, current_entry->remote_filename))
259 {
260 *entry = current_entry;
261 return true;
262 }
263 }
264
265 return false;
266 }
267
268 /* Fetches core updater list entry corresponding
269 * to the specified core.
270 * Returns false if core is not found. */
core_updater_list_get_core(core_updater_list_t * core_list,const char * local_core_path,const core_updater_list_entry_t ** entry)271 bool core_updater_list_get_core(
272 core_updater_list_t *core_list,
273 const char *local_core_path,
274 const core_updater_list_entry_t **entry)
275 {
276 bool resolve_symlinks;
277 size_t num_entries;
278 size_t i;
279 char real_core_path[PATH_MAX_LENGTH];
280
281 real_core_path[0] = '\0';
282
283 if (!core_list || !entry || string_is_empty(local_core_path))
284 return false;
285
286 num_entries = RBUF_LEN(core_list->entries);
287
288 if (num_entries < 1)
289 return false;
290
291 /* Resolve absolute pathname of local_core_path */
292 strlcpy(real_core_path, local_core_path, sizeof(real_core_path));
293 /* Can't resolve symlinks when dealing with cores
294 * installed via play feature delivery, because the
295 * source files have non-standard file names (which
296 * will not be recognised by regular core handling
297 * routines) */
298 resolve_symlinks = (core_list->type != CORE_UPDATER_LIST_TYPE_PFD);
299 path_resolve_realpath(real_core_path, sizeof(real_core_path),
300 resolve_symlinks);
301
302 if (string_is_empty(real_core_path))
303 return false;
304
305 /* Search for specified core */
306 for (i = 0; i < num_entries; i++)
307 {
308 core_updater_list_entry_t *current_entry = &core_list->entries[i];
309
310 if (string_is_empty(current_entry->local_core_path))
311 continue;
312
313 #ifdef _WIN32
314 /* Handle case-insensitive operating systems*/
315 if (string_is_equal_noncase(real_core_path, current_entry->local_core_path))
316 {
317 #else
318 if (string_is_equal(real_core_path, current_entry->local_core_path))
319 {
320 #endif
321 *entry = current_entry;
322 return true;
323 }
324 }
325
326 return false;
327 }
328
329 /***********/
330 /* Setters */
331 /***********/
332
333 /* Parses date string and adds contents to
334 * specified core updater list entry */
335 static bool core_updater_list_set_date(
336 core_updater_list_entry_t *entry, const char *date_str)
337 {
338 struct string_list date_list = {0};
339
340 if (!entry || string_is_empty(date_str))
341 goto error;
342
343 /* Split date string into component values */
344 string_list_initialize(&date_list);
345 if (!string_split_noalloc(&date_list, date_str, "-"))
346 goto error;
347
348 /* Date string must have 3 values:
349 * [year] [month] [day] */
350 if (date_list.size < 3)
351 goto error;
352
353 /* Convert date string values */
354 entry->date.year = string_to_unsigned(date_list.elems[0].data);
355 entry->date.month = string_to_unsigned(date_list.elems[1].data);
356 entry->date.day = string_to_unsigned(date_list.elems[2].data);
357
358 /* Clean up */
359 string_list_deinitialize(&date_list);
360
361 return true;
362
363 error:
364 string_list_deinitialize(&date_list);
365
366 return false;
367 }
368
369 /* Parses crc string and adds value to
370 * specified core updater list entry */
371 static bool core_updater_list_set_crc(
372 core_updater_list_entry_t *entry, const char *crc_str)
373 {
374 uint32_t crc;
375
376 if (!entry || string_is_empty(crc_str))
377 return false;
378
379 crc = (uint32_t)string_hex_to_unsigned(crc_str);
380
381 if (crc == 0)
382 return false;
383
384 entry->crc = crc;
385
386 return true;
387 }
388
389 /* Parses core filename string and adds all
390 * associated paths to the specified core
391 * updater list entry */
392 static bool core_updater_list_set_paths(
393 core_updater_list_entry_t *entry,
394 const char *path_dir_libretro,
395 const char *path_libretro_info,
396 const char *network_buildbot_url,
397 const char *filename_str,
398 enum core_updater_list_type list_type)
399 {
400 char *last_underscore = NULL;
401 char *tmp_url = NULL;
402 bool is_archive = true;
403 /* Can't resolve symlinks when dealing with cores
404 * installed via play feature delivery, because the
405 * source files have non-standard file names (which
406 * will not be recognised by regular core handling
407 * routines) */
408 bool resolve_symlinks = (list_type != CORE_UPDATER_LIST_TYPE_PFD);
409 char remote_core_path[PATH_MAX_LENGTH];
410 char local_core_path[PATH_MAX_LENGTH];
411 char local_info_path[PATH_MAX_LENGTH];
412
413 remote_core_path[0] = '\0';
414 local_core_path[0] = '\0';
415 local_info_path[0] = '\0';
416
417 if (!entry ||
418 string_is_empty(filename_str) ||
419 string_is_empty(path_dir_libretro) ||
420 string_is_empty(path_libretro_info))
421 return false;
422
423 /* Only buildbot cores require the buildbot URL */
424 if ((list_type == CORE_UPDATER_LIST_TYPE_BUILDBOT) &&
425 string_is_empty(network_buildbot_url))
426 return false;
427
428 /* Check whether remote file is an archive */
429 is_archive = path_is_compressed_file(filename_str);
430
431 /* remote_filename */
432 if (entry->remote_filename)
433 {
434 free(entry->remote_filename);
435 entry->remote_filename = NULL;
436 }
437
438 entry->remote_filename = strdup(filename_str);
439
440 /* remote_core_path
441 * > Leave blank if this is not a buildbot core */
442 if (list_type == CORE_UPDATER_LIST_TYPE_BUILDBOT)
443 {
444 fill_pathname_join(
445 remote_core_path,
446 network_buildbot_url,
447 filename_str,
448 sizeof(remote_core_path));
449
450 /* > Apply proper URL encoding (messy...) */
451 tmp_url = strdup(remote_core_path);
452 remote_core_path[0] = '\0';
453 net_http_urlencode_full(
454 remote_core_path, tmp_url, sizeof(remote_core_path));
455 if (tmp_url)
456 free(tmp_url);
457 }
458
459 if (entry->remote_core_path)
460 {
461 free(entry->remote_core_path);
462 entry->remote_core_path = NULL;
463 }
464
465 entry->remote_core_path = strdup(remote_core_path);
466
467 /* local_core_path */
468 fill_pathname_join(
469 local_core_path,
470 path_dir_libretro,
471 filename_str,
472 sizeof(local_core_path));
473
474 if (is_archive)
475 path_remove_extension(local_core_path);
476
477 path_resolve_realpath(local_core_path, sizeof(local_core_path),
478 resolve_symlinks);
479
480 if (entry->local_core_path)
481 {
482 free(entry->local_core_path);
483 entry->local_core_path = NULL;
484 }
485
486 entry->local_core_path = strdup(local_core_path);
487
488 /* local_info_path */
489 fill_pathname_join_noext(
490 local_info_path,
491 path_libretro_info,
492 filename_str,
493 sizeof(local_info_path));
494
495 if (is_archive)
496 path_remove_extension(local_info_path);
497
498 /* > Remove any non-standard core filename
499 * additions (i.e. info files end with
500 * '_libretro' but core files may have
501 * a platform specific addendum,
502 * e.g. '_android')*/
503 last_underscore = (char*)strrchr(local_info_path, '_');
504
505 if (!string_is_empty(last_underscore))
506 if (!string_is_equal(last_underscore, "_libretro"))
507 *last_underscore = '\0';
508
509 /* > Add proper file extension */
510 strlcat(
511 local_info_path,
512 FILE_PATH_CORE_INFO_EXTENSION,
513 sizeof(local_info_path));
514
515 if (entry->local_info_path)
516 {
517 free(entry->local_info_path);
518 entry->local_info_path = NULL;
519 }
520
521 entry->local_info_path = strdup(local_info_path);
522
523 return true;
524 }
525
526 /* Reads info file associated with core and
527 * adds relevant information to updater list
528 * entry */
529 static bool core_updater_list_set_core_info(
530 core_updater_list_entry_t *entry,
531 const char *local_info_path,
532 const char *filename_str)
533 {
534 core_updater_info_t *core_info = NULL;
535
536 if (!entry ||
537 string_is_empty(local_info_path) ||
538 string_is_empty(filename_str))
539 return false;
540
541 /* Clear any existing core info */
542 if (entry->display_name)
543 {
544 free(entry->display_name);
545 entry->display_name = NULL;
546 }
547
548 if (entry->description)
549 {
550 free(entry->description);
551 entry->description = NULL;
552 }
553
554 if (entry->licenses_list)
555 {
556 /* Note: We can safely leave this as NULL if
557 * the core info file is invalid */
558 string_list_free(entry->licenses_list);
559 entry->licenses_list = NULL;
560 }
561
562 entry->is_experimental = false;
563
564 /* Read core info file
565 * > Note: It's a bit rubbish that we have to
566 * read the actual core info files here...
567 * Would be better to cache this globally
568 * (at present, we only cache info for
569 * *installed* cores...) */
570 core_info = core_info_get_core_updater_info(local_info_path);
571
572 if (core_info)
573 {
574 /* display_name + is_experimental */
575 if (!string_is_empty(core_info->display_name))
576 {
577 entry->display_name = strdup(core_info->display_name);
578 entry->is_experimental = core_info->is_experimental;
579 }
580 else
581 {
582 /* If display name is blank, use core filename and
583 * assume core is experimental (i.e. all 'fit for consumption'
584 * cores must have a valid/complete core info file) */
585 entry->display_name = strdup(filename_str);
586 entry->is_experimental = true;
587 }
588
589 /* description */
590 if (!string_is_empty(core_info->description))
591 entry->description = strdup(core_info->description);
592 else
593 entry->description = strdup("");
594
595 /* licenses_list */
596 if (!string_is_empty(core_info->licenses))
597 entry->licenses_list = string_split(core_info->licenses, "|");
598
599 /* Clean up */
600 core_info_free_core_updater_info(core_info);
601 }
602 else
603 {
604 /* If info file is missing, use core filename and
605 * assume core is experimental (i.e. all 'fit for consumption'
606 * cores must have a valid/complete core info file) */
607 entry->display_name = strdup(filename_str);
608 entry->is_experimental = true;
609 entry->description = strdup("");
610 }
611
612 return true;
613 }
614
615 /* Adds entry to the end of the specified core
616 * updater list
617 * NOTE: Entry string values are passed by
618 * reference - *do not free the entry passed
619 * to this function* */
620 static bool core_updater_list_push_entry(
621 core_updater_list_t *core_list, core_updater_list_entry_t *entry)
622 {
623 core_updater_list_entry_t *list_entry = NULL;
624 size_t num_entries;
625
626 if (!core_list || !entry)
627 return false;
628
629 /* Get current number of list entries */
630 num_entries = RBUF_LEN(core_list->entries);
631
632 /* Attempt to allocate memory for new entry */
633 if (!RBUF_TRYFIT(core_list->entries, num_entries + 1))
634 return false;
635
636 /* Allocation successful - increment array size */
637 RBUF_RESIZE(core_list->entries, num_entries + 1);
638
639 /* Get handle of new entry at end of list, and
640 * zero-initialise members */
641 list_entry = &core_list->entries[num_entries];
642 memset(list_entry, 0, sizeof(*list_entry));
643
644 /* Assign paths */
645 list_entry->remote_filename = entry->remote_filename;
646 list_entry->remote_core_path = entry->remote_core_path;
647 list_entry->local_core_path = entry->local_core_path;
648 list_entry->local_info_path = entry->local_info_path;
649
650 /* Assign core info */
651 list_entry->display_name = entry->display_name;
652 list_entry->description = entry->description;
653 list_entry->licenses_list = entry->licenses_list;
654 list_entry->is_experimental = entry->is_experimental;
655
656 /* Copy crc */
657 list_entry->crc = entry->crc;
658
659 /* Copy date */
660 memcpy(&list_entry->date, &entry->date, sizeof(core_updater_list_date_t));
661
662 return true;
663 }
664
665 /* Parses the contents of a single buildbot
666 * core listing and adds it to the specified
667 * core updater list */
668 static void core_updater_list_add_entry(
669 core_updater_list_t *core_list,
670 const char *path_dir_libretro,
671 const char *path_libretro_info,
672 const char *network_buildbot_url,
673 struct string_list *network_core_entry_list)
674 {
675 const char *date_str = NULL;
676 const char *crc_str = NULL;
677 const char *filename_str = NULL;
678 const core_updater_list_entry_t *search_entry = NULL;
679 core_updater_list_entry_t entry = {0};
680
681 if (!core_list || !network_core_entry_list)
682 goto error;
683
684 /* > Listings must have 3 entries:
685 * [date] [crc] [filename] */
686 if (network_core_entry_list->size < 3)
687 goto error;
688
689 /* Get handles of the individual listing strings */
690 date_str = network_core_entry_list->elems[0].data;
691 crc_str = network_core_entry_list->elems[1].data;
692 filename_str = network_core_entry_list->elems[2].data;
693
694 if (string_is_empty(date_str) ||
695 string_is_empty(crc_str) ||
696 string_is_empty(filename_str))
697 goto error;
698
699 /* Check whether core file is already included
700 * in the list (this is *not* an error condition,
701 * it just means we can skip the current listing) */
702 if (core_updater_list_get_filename(core_list,
703 filename_str, &search_entry))
704 goto error;
705
706 /* Parse individual listing strings */
707 if (!core_updater_list_set_date(&entry, date_str))
708 goto error;
709
710 if (!core_updater_list_set_crc(&entry, crc_str))
711 goto error;
712
713 if (!core_updater_list_set_paths(
714 &entry,
715 path_dir_libretro,
716 path_libretro_info,
717 network_buildbot_url,
718 filename_str,
719 CORE_UPDATER_LIST_TYPE_BUILDBOT))
720 goto error;
721
722 if (!core_updater_list_set_core_info(
723 &entry,
724 entry.local_info_path,
725 filename_str))
726 goto error;
727
728 /* Add entry to list */
729 if (!core_updater_list_push_entry(core_list, &entry))
730 goto error;
731
732 return;
733
734 error:
735 /* This is not a *fatal* error - it just
736 * means one of the following:
737 * - The current line of entry text received
738 * from the buildbot is broken somehow
739 * (could be the case that the network buffer
740 * wasn't large enough to cache the entire
741 * string, so the last line was truncated)
742 * - We had insufficient memory to allocate a new
743 * entry in the core updater list
744 * In either case, the current entry is discarded
745 * and we move on to the next one
746 * (network transfers are fishy business, so we
747 * choose to ignore this sort of error - don't
748 * want the whole fetch to fail because of a
749 * trivial glitch...) */
750 core_updater_list_free_entry(&entry);
751 }
752
753 /* Core updater list qsort helper function */
754 static int core_updater_list_qsort_func(
755 const core_updater_list_entry_t *a, const core_updater_list_entry_t *b)
756 {
757 if (!a || !b)
758 return 0;
759
760 if (string_is_empty(a->display_name) || string_is_empty(b->display_name))
761 return 0;
762
763 return strcasecmp(a->display_name, b->display_name);
764 }
765
766 /* Sorts core updater list into alphabetical order */
767 static void core_updater_list_qsort(core_updater_list_t *core_list)
768 {
769 size_t num_entries;
770
771 if (!core_list)
772 return;
773
774 num_entries = RBUF_LEN(core_list->entries);
775
776 if (num_entries < 2)
777 return;
778
779 qsort(
780 core_list->entries, num_entries,
781 sizeof(core_updater_list_entry_t),
782 (int (*)(const void *, const void *))
783 core_updater_list_qsort_func);
784 }
785
786 /* Reads the contents of a buildbot core list
787 * network request into the specified
788 * core_updater_list_t object.
789 * Returns false in the event of an error. */
790 bool core_updater_list_parse_network_data(
791 core_updater_list_t *core_list,
792 const char *path_dir_libretro,
793 const char *path_libretro_info,
794 const char *network_buildbot_url,
795 const char *data, size_t len)
796 {
797 size_t i;
798 char *data_buf = NULL;
799 struct string_list network_core_list = {0};
800
801 /* Sanity check */
802 if (!core_list || string_is_empty(data) || (len < 1))
803 goto error;
804
805 /* We're populating a list 'from scratch' - remove
806 * any existing entries */
807 core_updater_list_reset(core_list);
808
809 /* Input data string is not terminated - have
810 * to copy it to a temporary buffer... */
811 data_buf = (char*)malloc((len + 1) * sizeof(char));
812
813 if (!data_buf)
814 goto error;
815
816 memcpy(data_buf, data, len * sizeof(char));
817 data_buf[len] = '\0';
818
819 /* Split network listing request into lines */
820 string_list_initialize(&network_core_list);
821 if (!string_split_noalloc(&network_core_list, data_buf, "\n"))
822 goto error;
823
824 if (network_core_list.size < 1)
825 goto error;
826
827 /* Temporary data buffer is no longer required */
828 free(data_buf);
829 data_buf = NULL;
830
831 /* Loop over lines */
832 for (i = 0; i < network_core_list.size; i++)
833 {
834 struct string_list network_core_entry_list = {0};
835 const char *line = network_core_list.elems[i].data;
836
837 if (string_is_empty(line))
838 continue;
839
840 string_list_initialize(&network_core_entry_list);
841 /* Split line into listings info components */
842 string_split_noalloc(&network_core_entry_list, line, " ");
843
844 /* Parse listings info and add to core updater
845 * list */
846 core_updater_list_add_entry(
847 core_list,
848 path_dir_libretro,
849 path_libretro_info,
850 network_buildbot_url,
851 &network_core_entry_list);
852
853 /* Clean up */
854 string_list_deinitialize(&network_core_entry_list);
855 }
856
857 /* Sanity check */
858 if (RBUF_LEN(core_list->entries) < 1)
859 goto error;
860
861 /* Clean up */
862 string_list_deinitialize(&network_core_list);
863
864 /* Sort completed list */
865 core_updater_list_qsort(core_list);
866
867 /* Set list type */
868 core_list->type = CORE_UPDATER_LIST_TYPE_BUILDBOT;
869
870 return true;
871
872 error:
873 string_list_deinitialize(&network_core_list);
874
875 if (data_buf)
876 free(data_buf);
877
878 return false;
879 }
880
881 /* Parses a single play feature delivery core
882 * listing and adds it to the specified core
883 * updater list */
884 static void core_updater_list_add_pfd_entry(
885 core_updater_list_t *core_list,
886 const char *path_dir_libretro,
887 const char *path_libretro_info,
888 const char *filename_str)
889 {
890 const core_updater_list_entry_t *search_entry = NULL;
891 core_updater_list_entry_t entry = {0};
892
893 if (!core_list || string_is_empty(filename_str))
894 goto error;
895
896 /* Check whether core file is already included
897 * in the list (this is *not* an error condition,
898 * it just means we can skip the current listing) */
899 if (core_updater_list_get_filename(core_list,
900 filename_str, &search_entry))
901 goto error;
902
903 /* Note: Play feature delivery cores have no
904 * timestamp or CRC info - leave these fields
905 * zero initialised */
906
907 /* Populate entry fields */
908 if (!core_updater_list_set_paths(
909 &entry,
910 path_dir_libretro,
911 path_libretro_info,
912 NULL,
913 filename_str,
914 CORE_UPDATER_LIST_TYPE_PFD))
915 goto error;
916
917 if (!core_updater_list_set_core_info(
918 &entry,
919 entry.local_info_path,
920 filename_str))
921 goto error;
922
923 /* Add entry to list */
924 if (!core_updater_list_push_entry(core_list, &entry))
925 goto error;
926
927 return;
928
929 error:
930 /* This is not a *fatal* error - it just
931 * means one of the following:
932 * - The core listing entry obtained from the
933 * play feature delivery interface is broken
934 * somehow
935 * - We had insufficient memory to allocate a new
936 * entry in the core updater list
937 * In either case, the current entry is discarded
938 * and we move on to the next one */
939 core_updater_list_free_entry(&entry);
940 }
941
942 /* Reads the list of cores currently available
943 * via play feature delivery (PFD) into the
944 * specified core_updater_list_t object.
945 * Returns false in the event of an error. */
946 bool core_updater_list_parse_pfd_data(
947 core_updater_list_t *core_list,
948 const char *path_dir_libretro,
949 const char *path_libretro_info,
950 const struct string_list *pfd_cores)
951 {
952 size_t i;
953
954 /* Sanity check */
955 if (!core_list || !pfd_cores || (pfd_cores->size < 1))
956 return false;
957
958 /* We're populating a list 'from scratch' - remove
959 * any existing entries */
960 core_updater_list_reset(core_list);
961
962 /* Loop over play feature delivery core list */
963 for (i = 0; i < pfd_cores->size; i++)
964 {
965 const char *filename_str = pfd_cores->elems[i].data;
966
967 if (string_is_empty(filename_str))
968 continue;
969
970 /* Parse core file name and add to core
971 * updater list */
972 core_updater_list_add_pfd_entry(
973 core_list,
974 path_dir_libretro,
975 path_libretro_info,
976 filename_str);
977 }
978
979 /* Sanity check */
980 if (RBUF_LEN(core_list->entries) < 1)
981 return false;
982
983 /* Sort completed list */
984 core_updater_list_qsort(core_list);
985
986 /* Set list type */
987 core_list->type = CORE_UPDATER_LIST_TYPE_PFD;
988
989 return true;
990 }
991