1 /* RetroArch - A frontend for libretro.
2 * Copyright (C) 2011-2017 - Daniel De Matteis
3 * Copyright (C) 2014-2017 - Jean-André Santoni
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 <math.h>
19 #include <compat/strcasestr.h>
20 #include <compat/strl.h>
21 #include <retro_miscellaneous.h>
22 #include <retro_endianness.h>
23 #include <string/stdstring.h>
24 #include <lists/dir_list.h>
25 #include <file/file_path.h>
26 #include <encodings/crc32.h>
27 #include <streams/file_stream.h>
28 #include <streams/chd_stream.h>
29 #include <streams/interface_stream.h>
30 #include "tasks_internal.h"
31
32 #include "../core_info.h"
33 #include "../database_info.h"
34
35 #include "../file_path_special.h"
36 #include "../msg_hash.h"
37 #include "../playlist.h"
38 #ifdef RARCH_INTERNAL
39 #include "../configuration.h"
40 #include "../retroarch.h"
41 #include "../ui/ui_companion_driver.h"
42 #include "../gfx/video_display_server.h"
43 #endif
44 #include "../verbosity.h"
45
46 typedef struct database_state_handle
47 {
48 database_info_list_t *info;
49 struct string_list *list;
50 uint8_t *buf;
51 size_t list_index;
52 size_t entry_index;
53 uint32_t crc;
54 uint32_t archive_crc;
55 char archive_name[511];
56 char serial[4096];
57 } database_state_handle_t;
58
59 typedef struct db_handle
60 {
61 char *playlist_directory;
62 char *content_database_path;
63 char *fullpath;
64 database_info_handle_t *handle;
65 database_state_handle_t state;
66 playlist_config_t playlist_config; /* size_t alignment */
67 unsigned status;
68 bool is_directory;
69 bool scan_started;
70 bool scan_without_core_match;
71 bool show_hidden_files;
72 } db_handle_t;
73
74 /* Forward declarations */
75 int cue_find_track(const char *cue_path, bool first,
76 uint64_t *offset, uint64_t *size,
77 char *track_path, uint64_t max_len);
78 bool cue_next_file(intfstream_t *fd, const char *cue_path,
79 char *path, uint64_t max_len);
80 int gdi_find_track(const char *gdi_path, bool first,
81 char *track_path, uint64_t max_len);
82 bool gdi_next_file(intfstream_t *fd, const char *gdi_path,
83 char *path, uint64_t max_len);
84 int detect_system(intfstream_t *fd, const char** system_name);
85 int detect_ps1_game(intfstream_t *fd, char *game_id);
86 int detect_psp_game(intfstream_t *fd, char *game_id);
87 int detect_gc_game(intfstream_t *fd, char *game_id);
88 int detect_serial_ascii_game(intfstream_t *fd, char *game_id);
89
database_info_get_current_name(database_state_handle_t * handle)90 static const char *database_info_get_current_name(
91 database_state_handle_t *handle)
92 {
93 if (!handle || !handle->list)
94 return NULL;
95 return handle->list->elems[handle->list_index].data;
96 }
97
database_info_get_current_element_name(database_info_handle_t * handle)98 static const char *database_info_get_current_element_name(
99 database_info_handle_t *handle)
100 {
101 if (!handle || !handle->list)
102 return NULL;
103 /* Skip pruned entries */
104 while (!handle->list->elems[handle->list_ptr].data)
105 {
106 if (++handle->list_ptr >= handle->list->size)
107 return NULL;
108 }
109 return handle->list->elems[handle->list_ptr].data;
110 }
111
task_database_iterate_start(retro_task_t * task,database_info_handle_t * db,const char * name)112 static int task_database_iterate_start(retro_task_t *task,
113 database_info_handle_t *db,
114 const char *name)
115 {
116 char msg[256];
117 const char *basename_path = !string_is_empty(name) ?
118 path_basename_nocompression(name) : "";
119
120 msg[0] = '\0';
121
122 snprintf(msg, sizeof(msg),
123 STRING_REP_USIZE "/" STRING_REP_USIZE ": %s %s...\n",
124 (size_t)db->list_ptr,
125 (size_t)db->list->size,
126 msg_hash_to_str(MSG_SCANNING),
127 basename_path);
128
129 if (!string_is_empty(msg))
130 {
131 #ifdef RARCH_INTERNAL
132 task_free_title(task);
133 task_set_title(task, strdup(msg));
134 if (db->list->size != 0)
135 task_set_progress(task,
136 roundf((float)db->list_ptr /
137 ((float)db->list->size / 100.0f)));
138 #else
139 fprintf(stderr, "msg: %s\n", msg);
140 #endif
141 }
142
143 db->status = DATABASE_STATUS_ITERATE;
144
145 return 0;
146 }
147
intfstream_get_serial(intfstream_t * fd,char * serial)148 static int intfstream_get_serial(intfstream_t *fd, char *serial)
149 {
150 const char *system_name = NULL;
151
152 /* Check if the system was not auto-detected. */
153 if (detect_system(fd, &system_name) < 0)
154 {
155 /* Attempt to read an ASCII serial, like Wii. */
156 if (detect_serial_ascii_game(fd, serial))
157 {
158 /* ASCII serial (Wii) was detected. */
159 RARCH_LOG("%s '%s'\n", msg_hash_to_str(MSG_FOUND_DISK_LABEL), serial);
160 return 0;
161 }
162
163 /* Any other non-system specific detection methods? */
164 return 0;
165 }
166
167 if (string_is_equal(system_name, "psp"))
168 {
169 if (detect_psp_game(fd, serial) == 0)
170 return 0;
171 RARCH_LOG("%s '%s'\n", msg_hash_to_str(MSG_FOUND_DISK_LABEL), serial);
172 }
173 else if (string_is_equal(system_name, "ps1"))
174 {
175 if (detect_ps1_game(fd, serial) == 0)
176 return 0;
177 RARCH_LOG("%s '%s'\n", msg_hash_to_str(MSG_FOUND_DISK_LABEL), serial);
178 }
179 else if (string_is_equal(system_name, "gc"))
180 {
181 if (detect_gc_game(fd, serial) == 0)
182 return 0;
183 RARCH_LOG("%s '%s'\n", msg_hash_to_str(MSG_FOUND_DISK_LABEL), serial);
184 }
185 else
186 return 0;
187
188 return 1;
189 }
190
intfstream_file_get_serial(const char * name,uint64_t offset,uint64_t size,char * serial)191 static bool intfstream_file_get_serial(const char *name,
192 uint64_t offset, uint64_t size, char *serial)
193 {
194 int rv;
195 uint8_t *data = NULL;
196 int64_t file_size = -1;
197 intfstream_t *fd = intfstream_open_file(name,
198 RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
199
200 if (!fd)
201 return 0;
202
203 if (intfstream_seek(fd, 0, SEEK_END) == -1)
204 goto error;
205
206 file_size = intfstream_tell(fd);
207
208 if (intfstream_seek(fd, 0, SEEK_SET) == -1)
209 goto error;
210
211 if (file_size < 0)
212 goto error;
213
214 if (offset != 0 || size < (uint64_t) file_size)
215 {
216 if (intfstream_seek(fd, (int64_t)offset, SEEK_SET) == -1)
217 goto error;
218
219 data = (uint8_t*)malloc((size_t)size);
220
221 if (intfstream_read(fd, data, size) != (int64_t) size)
222 {
223 free(data);
224 goto error;
225 }
226
227 intfstream_close(fd);
228 free(fd);
229 fd = intfstream_open_memory(data, RETRO_VFS_FILE_ACCESS_READ,
230 RETRO_VFS_FILE_ACCESS_HINT_NONE,
231 size);
232 if (!fd)
233 {
234 free(data);
235 return 0;
236 }
237 }
238
239 rv = intfstream_get_serial(fd, serial);
240 intfstream_close(fd);
241 free(fd);
242 free(data);
243 return rv;
244
245 error:
246 intfstream_close(fd);
247 free(fd);
248 return 0;
249 }
250
task_database_cue_get_serial(const char * name,char * serial)251 static int task_database_cue_get_serial(const char *name, char* serial)
252 {
253 char track_path[PATH_MAX_LENGTH];
254 uint64_t offset = 0;
255 uint64_t size = 0;
256 int rv = 0;
257
258 track_path[0] = '\0';
259
260 rv = cue_find_track(name, true, &offset, &size, track_path, sizeof(track_path));
261
262 if (rv < 0)
263 {
264 RARCH_LOG("%s: %s\n",
265 msg_hash_to_str(MSG_COULD_NOT_FIND_VALID_DATA_TRACK),
266 strerror(-rv));
267 return 0;
268 }
269
270 RARCH_LOG("%s\n", msg_hash_to_str(MSG_READING_FIRST_DATA_TRACK));
271
272 return intfstream_file_get_serial(track_path, offset, size, serial);
273 }
274
task_database_gdi_get_serial(const char * name,char * serial)275 static int task_database_gdi_get_serial(const char *name, char* serial)
276 {
277 char track_path[PATH_MAX_LENGTH];
278 int rv = 0;
279
280 track_path[0] = '\0';
281
282 rv = gdi_find_track(name, true, track_path, sizeof(track_path));
283
284 if (rv < 0)
285 {
286 RARCH_LOG("%s: %s\n",
287 msg_hash_to_str(MSG_COULD_NOT_FIND_VALID_DATA_TRACK),
288 strerror(-rv));
289 return 0;
290 }
291
292 RARCH_LOG("%s\n", msg_hash_to_str(MSG_READING_FIRST_DATA_TRACK));
293
294 return intfstream_file_get_serial(track_path, 0, SIZE_MAX, serial);
295 }
296
task_database_chd_get_serial(const char * name,char * serial)297 static int task_database_chd_get_serial(const char *name, char* serial)
298 {
299 int result;
300 intfstream_t *fd = intfstream_open_chd_track(
301 name,
302 RETRO_VFS_FILE_ACCESS_READ,
303 RETRO_VFS_FILE_ACCESS_HINT_NONE,
304 CHDSTREAM_TRACK_FIRST_DATA);
305 if (!fd)
306 return 0;
307
308 result = intfstream_get_serial(fd, serial);
309 intfstream_close(fd);
310 free(fd);
311 return result;
312 }
313
intfstream_file_get_crc(const char * name,uint64_t offset,size_t size,uint32_t * crc)314 static bool intfstream_file_get_crc(const char *name,
315 uint64_t offset, size_t size, uint32_t *crc)
316 {
317 bool rv;
318 intfstream_t *fd = intfstream_open_file(name,
319 RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
320 uint8_t *data = NULL;
321 int64_t file_size = -1;
322
323 if (!fd)
324 return 0;
325
326 if (intfstream_seek(fd, 0, SEEK_END) == -1)
327 goto error;
328
329 file_size = intfstream_tell(fd);
330
331 if (intfstream_seek(fd, 0, SEEK_SET) == -1)
332 goto error;
333
334 if (file_size < 0)
335 goto error;
336
337 if (offset != 0 || size < (uint64_t) file_size)
338 {
339 if (intfstream_seek(fd, (int64_t)offset, SEEK_SET) == -1)
340 goto error;
341
342 data = (uint8_t*)malloc(size);
343
344 if (intfstream_read(fd, data, size) != (int64_t) size)
345 goto error;
346
347 intfstream_close(fd);
348 free(fd);
349 fd = intfstream_open_memory(data, RETRO_VFS_FILE_ACCESS_READ,
350 RETRO_VFS_FILE_ACCESS_HINT_NONE, size);
351
352 if (!fd)
353 goto error;
354 }
355
356 rv = intfstream_get_crc(fd, crc);
357 intfstream_close(fd);
358 free(fd);
359 free(data);
360 return rv;
361
362 error:
363 if (fd)
364 {
365 intfstream_close(fd);
366 free(fd);
367 }
368 if (data)
369 free(data);
370 return 0;
371 }
372
task_database_cue_get_crc(const char * name,uint32_t * crc)373 static int task_database_cue_get_crc(const char *name, uint32_t *crc)
374 {
375 char track_path[PATH_MAX_LENGTH];
376 uint64_t offset = 0;
377 uint64_t size = 0;
378 int rv = 0;
379
380 track_path[0] = '\0';
381
382 rv = cue_find_track(name, false, &offset, &size,
383 track_path, sizeof(track_path));
384
385 if (rv < 0)
386 {
387 RARCH_LOG("%s: %s\n",
388 msg_hash_to_str(MSG_COULD_NOT_FIND_VALID_DATA_TRACK),
389 strerror(-rv));
390 return 0;
391 }
392
393 RARCH_LOG("CUE '%s' primary track: %s\n (%lu, %lu)\n",name, track_path, (unsigned long) offset, (unsigned long) size);
394
395 RARCH_LOG("%s\n", msg_hash_to_str(MSG_READING_FIRST_DATA_TRACK));
396
397 rv = intfstream_file_get_crc(track_path, offset, (size_t)size, crc);
398 if (rv == 1)
399 {
400 RARCH_LOG("CUE '%s' crc: %x\n", name, *crc);
401 }
402 return rv;
403 }
404
task_database_gdi_get_crc(const char * name,uint32_t * crc)405 static int task_database_gdi_get_crc(const char *name, uint32_t *crc)
406 {
407 char track_path[PATH_MAX_LENGTH];
408 int rv = 0;
409
410 track_path[0] = '\0';
411
412 rv = gdi_find_track(name, true, track_path, sizeof(track_path));
413
414 if (rv < 0)
415 {
416 RARCH_LOG("%s: %s\n", msg_hash_to_str(MSG_COULD_NOT_FIND_VALID_DATA_TRACK),
417 strerror(-rv));
418 return 0;
419 }
420
421 RARCH_LOG("GDI '%s' primary track: %s\n", name, track_path);
422
423 RARCH_LOG("%s\n", msg_hash_to_str(MSG_READING_FIRST_DATA_TRACK));
424
425 rv = intfstream_file_get_crc(track_path, 0, SIZE_MAX, crc);
426 if (rv == 1)
427 {
428 RARCH_LOG("GDI '%s' crc: %x\n", name, *crc);
429 }
430 return rv;
431 }
432
task_database_chd_get_crc(const char * name,uint32_t * crc)433 static bool task_database_chd_get_crc(const char *name, uint32_t *crc)
434 {
435 bool rv;
436 intfstream_t *fd = intfstream_open_chd_track(
437 name,
438 RETRO_VFS_FILE_ACCESS_READ,
439 RETRO_VFS_FILE_ACCESS_HINT_NONE,
440 CHDSTREAM_TRACK_PRIMARY);
441 if (!fd)
442 return 0;
443
444 rv = intfstream_get_crc(fd, crc);
445 if (rv)
446 {
447 RARCH_LOG("CHD '%s' crc: %x\n", name, *crc);
448 }
449 if (fd)
450 {
451 intfstream_close(fd);
452 free(fd);
453 }
454 return rv;
455 }
456
task_database_cue_prune(database_info_handle_t * db,const char * name)457 static void task_database_cue_prune(database_info_handle_t *db,
458 const char *name)
459 {
460 size_t i;
461 char path[PATH_MAX_LENGTH];
462 intfstream_t *fd = intfstream_open_file(name,
463 RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
464
465 if (!fd)
466 return;
467
468 path[0] = '\0';
469
470 while (cue_next_file(fd, name, path, sizeof(path)))
471 {
472 for (i = db->list_ptr; i < db->list->size; ++i)
473 {
474 if (db->list->elems[i].data
475 && string_is_equal(path, db->list->elems[i].data))
476 {
477 RARCH_LOG("Pruning file referenced by cue: %s\n", path);
478 free(db->list->elems[i].data);
479 db->list->elems[i].data = NULL;
480 }
481 }
482 }
483
484 intfstream_close(fd);
485 free(fd);
486 }
487
gdi_prune(database_info_handle_t * db,const char * name)488 static void gdi_prune(database_info_handle_t *db, const char *name)
489 {
490 size_t i;
491 char path[PATH_MAX_LENGTH];
492 intfstream_t *fd = intfstream_open_file(name,
493 RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
494
495 if (!fd)
496 return;
497
498 path[0] = '\0';
499
500 while (gdi_next_file(fd, name, path, sizeof(path)))
501 {
502 for (i = db->list_ptr; i < db->list->size; ++i)
503 {
504 if (db->list->elems[i].data
505 && string_is_equal(path, db->list->elems[i].data))
506 {
507 RARCH_LOG("Pruning file referenced by gdi: %s\n", path);
508 free(db->list->elems[i].data);
509 db->list->elems[i].data = NULL;
510 }
511 }
512 }
513
514 free(fd);
515 }
516
extension_to_file_type(const char * ext)517 static enum msg_file_type extension_to_file_type(const char *ext)
518 {
519 char ext_lower[6];
520
521 ext_lower[0] = '\0';
522
523 /* Copy and convert to lower case */
524 strlcpy(ext_lower, ext, sizeof(ext_lower));
525 string_to_lower(ext_lower);
526
527 if (
528 string_is_equal(ext_lower, "7z") ||
529 string_is_equal(ext_lower, "zip") ||
530 string_is_equal(ext_lower, "apk")
531 )
532 return FILE_TYPE_COMPRESSED;
533 if (
534 string_is_equal(ext_lower, "cue")
535 )
536 return FILE_TYPE_CUE;
537 if (
538 string_is_equal(ext_lower, "gdi")
539 )
540 return FILE_TYPE_GDI;
541 if (
542 string_is_equal(ext_lower, "iso")
543 )
544 return FILE_TYPE_ISO;
545 if (
546 string_is_equal(ext_lower, "chd")
547 )
548 return FILE_TYPE_CHD;
549 if (
550 string_is_equal(ext_lower, "wbfs")
551 )
552 return FILE_TYPE_WBFS;
553 if (
554 string_is_equal(ext_lower, "lutro")
555 )
556 return FILE_TYPE_LUTRO;
557 return FILE_TYPE_NONE;
558 }
559
task_database_iterate_playlist(database_state_handle_t * db_state,database_info_handle_t * db,const char * name)560 static int task_database_iterate_playlist(
561 database_state_handle_t *db_state,
562 database_info_handle_t *db, const char *name)
563 {
564 switch (extension_to_file_type(path_get_extension(name)))
565 {
566 case FILE_TYPE_COMPRESSED:
567 #ifdef HAVE_COMPRESSION
568 db->type = DATABASE_TYPE_CRC_LOOKUP;
569 /* first check crc of archive itself */
570 return intfstream_file_get_crc(name,
571 0, SIZE_MAX, &db_state->archive_crc);
572 #else
573 break;
574 #endif
575 case FILE_TYPE_CUE:
576 task_database_cue_prune(db, name);
577 db_state->serial[0] = '\0';
578 if (task_database_cue_get_serial(name, db_state->serial))
579 db->type = DATABASE_TYPE_SERIAL_LOOKUP;
580 else
581 {
582 db->type = DATABASE_TYPE_CRC_LOOKUP;
583 return task_database_cue_get_crc(name, &db_state->crc);
584 }
585 break;
586 case FILE_TYPE_GDI:
587 gdi_prune(db, name);
588 db_state->serial[0] = '\0';
589 /* There are no serial databases, so don't bother with
590 serials at the moment */
591 if (0 && task_database_gdi_get_serial(name, db_state->serial))
592 db->type = DATABASE_TYPE_SERIAL_LOOKUP;
593 else
594 {
595 db->type = DATABASE_TYPE_CRC_LOOKUP;
596 return task_database_gdi_get_crc(name, &db_state->crc);
597 }
598 break;
599 /* Consider Wii WBFS files similar to ISO files. */
600 case FILE_TYPE_WBFS:
601 case FILE_TYPE_ISO:
602 db_state->serial[0] = '\0';
603 intfstream_file_get_serial(name, 0, SIZE_MAX, db_state->serial);
604 db->type = DATABASE_TYPE_SERIAL_LOOKUP;
605 break;
606 case FILE_TYPE_CHD:
607 db_state->serial[0] = '\0';
608 if (task_database_chd_get_serial(name, db_state->serial))
609 db->type = DATABASE_TYPE_SERIAL_LOOKUP;
610 else
611 {
612 db->type = DATABASE_TYPE_CRC_LOOKUP;
613 return task_database_chd_get_crc(name, &db_state->crc);
614 }
615 break;
616 case FILE_TYPE_LUTRO:
617 db->type = DATABASE_TYPE_ITERATE_LUTRO;
618 break;
619 default:
620 db->type = DATABASE_TYPE_CRC_LOOKUP;
621 return intfstream_file_get_crc(name, 0, SIZE_MAX, &db_state->crc);
622 }
623
624 return 1;
625 }
626
database_info_list_iterate_end_no_match(database_info_handle_t * db,database_state_handle_t * db_state,const char * path,bool path_contains_compressed_file)627 static int database_info_list_iterate_end_no_match(
628 database_info_handle_t *db,
629 database_state_handle_t *db_state,
630 const char *path,
631 bool path_contains_compressed_file)
632 {
633 /* Reached end of database list,
634 * CRC match probably didn't succeed. */
635
636 /* If this was a compressed file and no match in the database
637 * list was found then expand the search list to include the
638 * archive's contents. */
639 if (!path_contains_compressed_file && path_is_compressed_file(path))
640 {
641 struct string_list *archive_list =
642 file_archive_get_file_list(path, NULL);
643
644 if (archive_list && archive_list->size > 0)
645 {
646 unsigned i;
647 size_t path_len = strlen(path);
648
649 for (i = 0; i < archive_list->size; i++)
650 {
651 if (path_len + strlen(archive_list->elems[i].data)
652 + 1 < PATH_MAX_LENGTH)
653 {
654 char new_path[PATH_MAX_LENGTH];
655 new_path[0] = '\0';
656 strlcpy(new_path, path, sizeof(new_path));
657 new_path[path_len] = '#';
658 strlcpy(new_path + path_len + 1,
659 archive_list->elems[i].data,
660 sizeof(new_path) - path_len);
661 string_list_append(db->list, new_path,
662 archive_list->elems[i].attr);
663 }
664 else
665 string_list_append(db->list, path,
666 archive_list->elems[i].attr);
667 }
668
669 string_list_free(archive_list);
670 }
671 }
672
673 db_state->list_index = 0;
674 db_state->entry_index = 0;
675
676 if (db_state->crc != 0)
677 db_state->crc = 0;
678
679 if (db_state->archive_crc != 0)
680 db_state->archive_crc = 0;
681
682 return 0;
683 }
684
database_info_list_iterate_new(database_state_handle_t * db_state,const char * query)685 static int database_info_list_iterate_new(database_state_handle_t *db_state,
686 const char *query)
687 {
688 const char *new_database = database_info_get_current_name(db_state);
689
690 #ifndef RARCH_INTERNAL
691 fprintf(stderr, "Check database [%d/%d] : %s\n",
692 (unsigned)db_state->list_index,
693 (unsigned)db_state->list->size, new_database);
694 #endif
695 if (db_state->info)
696 {
697 database_info_list_free(db_state->info);
698 free(db_state->info);
699 }
700 db_state->info = database_info_list_new(new_database, query);
701 return 0;
702 }
703
database_info_list_iterate_found_match(db_handle_t * _db,database_state_handle_t * db_state,database_info_handle_t * db,const char * archive_name)704 static int database_info_list_iterate_found_match(
705 db_handle_t *_db,
706 database_state_handle_t *db_state,
707 database_info_handle_t *db,
708 const char *archive_name
709 )
710 {
711 /* TODO/FIXME - heap allocations are done here to avoid
712 * running out of stack space on systems with a limited stack size.
713 * We should use less fullsize paths in the future so that we don't
714 * need to have all these big char arrays here */
715 size_t str_len = PATH_MAX_LENGTH * sizeof(char);
716 char* db_crc = (char*)malloc(str_len);
717 char* db_playlist_base_str = (char*)malloc(str_len);
718 char* db_playlist_path = (char*)malloc(str_len);
719 char* entry_path_str = (char*)malloc(str_len);
720 char *hash = NULL;
721 playlist_t *playlist = NULL;
722 const char *db_path =
723 database_info_get_current_name(db_state);
724 const char *entry_path =
725 database_info_get_current_element_name(db);
726 database_info_t *db_info_entry =
727 &db_state->info->list[db_state->entry_index];
728
729 db_crc[0] = '\0';
730 db_playlist_path[0] = '\0';
731 db_playlist_base_str[0] = '\0';
732 entry_path_str[0] = '\0';
733
734 fill_short_pathname_representation_noext(db_playlist_base_str,
735 db_path, str_len);
736
737 strlcat(db_playlist_base_str, ".lpl", str_len);
738
739 if (!string_is_empty(_db->playlist_directory))
740 fill_pathname_join(db_playlist_path, _db->playlist_directory,
741 db_playlist_base_str, str_len);
742
743 playlist_config_set_path(&_db->playlist_config, db_playlist_path);
744 playlist = playlist_init(&_db->playlist_config);
745
746 snprintf(db_crc, str_len, "%08X|crc", db_info_entry->crc32);
747
748 if (entry_path)
749 strlcpy(entry_path_str, entry_path, str_len);
750
751 if (!string_is_empty(archive_name))
752 fill_pathname_join_delim(entry_path_str,
753 entry_path_str, archive_name, '#', str_len);
754
755 if (core_info_database_match_archive_member(
756 db_state->list->elems[db_state->list_index].data) &&
757 (hash = strchr(entry_path_str, '#')))
758 *hash = '\0';
759
760 #if defined(RARCH_INTERNAL)
761 #if 0
762 RARCH_LOG("Found match in database !\n");
763
764 RARCH_LOG("Path: %s\n", db_path);
765 RARCH_LOG("CRC : %s\n", db_crc);
766 RARCH_LOG("Playlist Path: %s\n", db_playlist_path);
767 RARCH_LOG("Entry Path: %s\n", entry_path);
768 RARCH_LOG("Playlist not NULL: %d\n", playlist != NULL);
769 RARCH_LOG("ZIP entry: %s\n", archive_name);
770 RARCH_LOG("entry path str: %s\n", entry_path_str);
771 #endif
772 #else
773 fprintf(stderr, "Found match in database !\n");
774
775 fprintf(stderr, "Path: %s\n", db_path);
776 fprintf(stderr, "CRC : %s\n", db_crc);
777 fprintf(stderr, "Playlist Path: %s\n", db_playlist_path);
778 fprintf(stderr, "Entry Path: %s\n", entry_path);
779 fprintf(stderr, "Playlist not NULL: %d\n", playlist != NULL);
780 fprintf(stderr, "ZIP entry: %s\n", archive_name);
781 fprintf(stderr, "entry path str: %s\n", entry_path_str);
782 #endif
783
784 if (!playlist_entry_exists(playlist, entry_path_str))
785 {
786 struct playlist_entry entry;
787
788 /* the push function reads our entry as const,
789 * so these casts are safe */
790 entry.path = entry_path_str;
791 entry.label = db_info_entry->name;
792 entry.core_path = (char*)"DETECT";
793 entry.core_name = (char*)"DETECT";
794 entry.db_name = db_playlist_base_str;
795 entry.crc32 = db_crc;
796 entry.subsystem_ident = NULL;
797 entry.subsystem_name = NULL;
798 entry.subsystem_roms = NULL;
799 entry.runtime_hours = 0;
800 entry.runtime_minutes = 0;
801 entry.runtime_seconds = 0;
802 entry.last_played_year = 0;
803 entry.last_played_month = 0;
804 entry.last_played_day = 0;
805 entry.last_played_hour = 0;
806 entry.last_played_minute= 0;
807 entry.last_played_second= 0;
808
809 playlist_push(playlist, &entry);
810 }
811
812 playlist_write_file(playlist);
813 playlist_free(playlist);
814
815 database_info_list_free(db_state->info);
816 free(db_state->info);
817
818 db_state->info = NULL;
819 db_state->crc = 0;
820 db_state->archive_crc = 0;
821
822 /* Move database to start since we are likely to match against it
823 again */
824 if (db_state->list_index != 0)
825 {
826 struct string_list_elem entry =
827 db_state->list->elems[db_state->list_index];
828 memmove(&db_state->list->elems[1],
829 &db_state->list->elems[0],
830 sizeof(entry) * db_state->list_index);
831 db_state->list->elems[0] = entry;
832 }
833
834 free(db_crc);
835 free(db_playlist_base_str);
836 free(db_playlist_path);
837 free(entry_path_str);
838 return 0;
839 }
840
841 /* End of entries in database info list and didn't find a
842 * match, go to the next database. */
database_info_list_iterate_next(database_state_handle_t * db_state)843 static int database_info_list_iterate_next(
844 database_state_handle_t *db_state
845 )
846 {
847 db_state->list_index++;
848 db_state->entry_index = 0;
849
850 database_info_list_free(db_state->info);
851 free(db_state->info);
852 db_state->info = NULL;
853
854 return 1;
855 }
856
task_database_iterate_crc_lookup(db_handle_t * _db,database_state_handle_t * db_state,database_info_handle_t * db,const char * name,const char * archive_entry,bool path_contains_compressed_file)857 static int task_database_iterate_crc_lookup(
858 db_handle_t *_db,
859 database_state_handle_t *db_state,
860 database_info_handle_t *db,
861 const char *name,
862 const char *archive_entry,
863 bool path_contains_compressed_file)
864 {
865 if (!db_state->list ||
866 (unsigned)db_state->list_index == (unsigned)db_state->list->size)
867 return database_info_list_iterate_end_no_match(db, db_state, name,
868 path_contains_compressed_file);
869
870 /* Archive did not contain a CRC for this entry,
871 * or the file is empty. */
872 if (!db_state->crc)
873 {
874 db_state->crc = file_archive_get_file_crc32(name);
875
876 if (!db_state->crc)
877 return database_info_list_iterate_next(db_state);
878 }
879
880 if (db_state->entry_index == 0)
881 {
882 char query[50];
883
884 query[0] = '\0';
885
886 if (!_db->scan_without_core_match)
887 {
888 /* don't scan files that can't be in this database.
889 *
890 * Could be because of:
891 * - A matching core missing
892 * - Incompatible file extension */
893 if (!core_info_database_supports_content_path(
894 db_state->list->elems[db_state->list_index].data, name))
895 return database_info_list_iterate_next(db_state);
896
897 if (!path_contains_compressed_file)
898 {
899 if (core_info_database_match_archive_member(
900 db_state->list->elems[db_state->list_index].data))
901 return database_info_list_iterate_next(db_state);
902 }
903 }
904
905 snprintf(query, sizeof(query),
906 "{crc:or(b\"%08X\",b\"%08X\")}",
907 db_state->crc, db_state->archive_crc);
908
909 database_info_list_iterate_new(db_state, query);
910 }
911
912 if (db_state->info)
913 {
914 database_info_t *db_info_entry =
915 &db_state->info->list[db_state->entry_index];
916
917 if (db_info_entry && db_info_entry->crc32)
918 {
919 #if 0
920 RARCH_LOG("CRC32: 0x%08X , entry CRC32: 0x%08X (%s).\n",
921 db_state->crc, db_info_entry->crc32, db_info_entry->name);
922 #endif
923 if (db_state->archive_crc == db_info_entry->crc32)
924 return database_info_list_iterate_found_match(
925 _db,
926 db_state, db, NULL);
927 if (db_state->crc == db_info_entry->crc32)
928 return database_info_list_iterate_found_match(
929 _db,
930 db_state, db, archive_entry);
931 }
932 }
933
934 db_state->entry_index++;
935
936 if (db_state->info)
937 {
938 if (db_state->entry_index >= db_state->info->count)
939 return database_info_list_iterate_next(db_state);
940 }
941
942 /* If we haven't reached the end of the database list yet,
943 * continue iterating. */
944 if (db_state->list_index < db_state->list->size)
945 return 1;
946
947 database_info_list_free(db_state->info);
948
949 if (db_state->info)
950 free(db_state->info);
951
952 return 0;
953 }
954
task_database_iterate_playlist_lutro(db_handle_t * _db,database_state_handle_t * db_state,database_info_handle_t * db,const char * path)955 static int task_database_iterate_playlist_lutro(
956 db_handle_t *_db,
957 database_state_handle_t *db_state,
958 database_info_handle_t *db,
959 const char *path)
960 {
961 char db_playlist_path[PATH_MAX_LENGTH];
962 playlist_t *playlist = NULL;
963
964 db_playlist_path[0] = '\0';
965
966 if (!string_is_empty(_db->playlist_directory))
967 fill_pathname_join(db_playlist_path,
968 _db->playlist_directory,
969 "Lutro.lpl", sizeof(db_playlist_path));
970
971 playlist_config_set_path(&_db->playlist_config, db_playlist_path);
972 playlist = playlist_init(&_db->playlist_config);
973
974 if (!playlist_entry_exists(playlist, path))
975 {
976 struct playlist_entry entry;
977 char game_title[PATH_MAX_LENGTH];
978
979 game_title[0] = '\0';
980
981 fill_short_pathname_representation_noext(game_title,
982 path, sizeof(game_title));
983
984 /* the push function reads our entry as const,
985 * so these casts are safe */
986 entry.path = (char*)path;
987 entry.label = game_title;
988 entry.core_path = (char*)"DETECT";
989 entry.core_name = (char*)"DETECT";
990 entry.db_name = (char*)"Lutro.lpl";
991 entry.crc32 = (char*)"DETECT";
992 entry.subsystem_ident = NULL;
993 entry.subsystem_name = NULL;
994 entry.subsystem_roms = NULL;
995 entry.runtime_hours = 0;
996 entry.runtime_minutes = 0;
997 entry.runtime_seconds = 0;
998 entry.last_played_year = 0;
999 entry.last_played_month = 0;
1000 entry.last_played_day = 0;
1001 entry.last_played_hour = 0;
1002 entry.last_played_minute = 0;
1003 entry.last_played_second = 0;
1004
1005 playlist_push(playlist, &entry);
1006 }
1007
1008 playlist_write_file(playlist);
1009 playlist_free(playlist);
1010
1011 return 0;
1012 }
1013
task_database_iterate_serial_lookup(db_handle_t * _db,database_state_handle_t * db_state,database_info_handle_t * db,const char * name,bool path_contains_compressed_file)1014 static int task_database_iterate_serial_lookup(
1015 db_handle_t *_db,
1016 database_state_handle_t *db_state,
1017 database_info_handle_t *db, const char *name,
1018 bool path_contains_compressed_file
1019 )
1020 {
1021 if (
1022 !db_state->list ||
1023 (unsigned)db_state->list_index == (unsigned)db_state->list->size
1024 )
1025 return database_info_list_iterate_end_no_match(db, db_state, name,
1026 path_contains_compressed_file);
1027
1028 if (db_state->entry_index == 0)
1029 {
1030 char query[50];
1031 char *serial_buf = bin_to_hex_alloc(
1032 (uint8_t*)db_state->serial,
1033 strlen(db_state->serial) * sizeof(uint8_t));
1034
1035 if (!serial_buf)
1036 return 1;
1037
1038 query[0] = '\0';
1039
1040 snprintf(query, sizeof(query), "{'serial': b'%s'}", serial_buf);
1041 database_info_list_iterate_new(db_state, query);
1042
1043 free(serial_buf);
1044 }
1045
1046 if (db_state->info)
1047 {
1048 database_info_t *db_info_entry = &db_state->info->list[
1049 db_state->entry_index];
1050
1051 if (db_info_entry && db_info_entry->serial)
1052 {
1053 #if 0
1054 RARCH_LOG("serial: %s , entry serial: %s (%s).\n",
1055 db_state->serial, db_info_entry->serial,
1056 db_info_entry->name);
1057 #endif
1058 if (string_is_equal(db_state->serial, db_info_entry->serial))
1059 return database_info_list_iterate_found_match(_db,
1060 db_state, db, NULL);
1061 }
1062 }
1063
1064 db_state->entry_index++;
1065
1066 if (db_state->info)
1067 {
1068 if (db_state->entry_index >= db_state->info->count)
1069 return database_info_list_iterate_next(db_state);
1070 }
1071
1072 /* If we haven't reached the end of the database list yet,
1073 * continue iterating. */
1074 if (db_state->list_index < db_state->list->size)
1075 return 1;
1076
1077 database_info_list_free(db_state->info);
1078 free(db_state->info);
1079 return 0;
1080 }
1081
task_database_iterate(db_handle_t * _db,const char * name,database_state_handle_t * db_state,database_info_handle_t * db,bool path_contains_compressed_file)1082 static int task_database_iterate(
1083 db_handle_t *_db,
1084 const char *name,
1085 database_state_handle_t *db_state,
1086 database_info_handle_t *db,
1087 bool path_contains_compressed_file)
1088 {
1089 switch (db->type)
1090 {
1091 case DATABASE_TYPE_ITERATE:
1092 return task_database_iterate_playlist(db_state, db, name);
1093 case DATABASE_TYPE_ITERATE_ARCHIVE:
1094 #ifdef HAVE_COMPRESSION
1095 return task_database_iterate_crc_lookup(
1096 _db, db_state, db, name, db_state->archive_name,
1097 path_contains_compressed_file);
1098 #else
1099 return 1;
1100 #endif
1101 case DATABASE_TYPE_ITERATE_LUTRO:
1102 return task_database_iterate_playlist_lutro(_db, db_state, db, name);
1103 case DATABASE_TYPE_SERIAL_LOOKUP:
1104 return task_database_iterate_serial_lookup(_db, db_state, db, name,
1105 path_contains_compressed_file);
1106 case DATABASE_TYPE_CRC_LOOKUP:
1107 return task_database_iterate_crc_lookup(_db, db_state, db, name, NULL,
1108 path_contains_compressed_file);
1109 case DATABASE_TYPE_NONE:
1110 default:
1111 break;
1112 }
1113
1114 return 0;
1115 }
1116
task_database_cleanup_state(database_state_handle_t * db_state)1117 static void task_database_cleanup_state(
1118 database_state_handle_t *db_state)
1119 {
1120 if (!db_state)
1121 return;
1122
1123 if (db_state->buf)
1124 free(db_state->buf);
1125 db_state->buf = NULL;
1126 }
1127
task_database_handler(retro_task_t * task)1128 static void task_database_handler(retro_task_t *task)
1129 {
1130 const char *name = NULL;
1131 database_info_handle_t *dbinfo = NULL;
1132 database_state_handle_t *dbstate = NULL;
1133 db_handle_t *db = NULL;
1134
1135 if (!task)
1136 goto task_finished;
1137
1138 db = (db_handle_t*)task->state;
1139
1140 if (!db)
1141 goto task_finished;
1142
1143 if (!db->scan_started)
1144 {
1145 db->scan_started = true;
1146
1147 if (!string_is_empty(db->fullpath))
1148 {
1149 if (db->is_directory)
1150 db->handle = database_info_dir_init(
1151 db->fullpath, DATABASE_TYPE_ITERATE,
1152 task, db->show_hidden_files);
1153 else
1154 db->handle = database_info_file_init(
1155 db->fullpath, DATABASE_TYPE_ITERATE,
1156 task);
1157 }
1158
1159 if (db->handle)
1160 db->handle->status = DATABASE_STATUS_ITERATE_BEGIN;
1161 }
1162
1163 dbinfo = db->handle;
1164 dbstate = &db->state;
1165
1166 if (!dbinfo || task_get_cancelled(task))
1167 goto task_finished;
1168
1169 switch (dbinfo->status)
1170 {
1171 case DATABASE_STATUS_ITERATE_BEGIN:
1172 if (dbstate && !dbstate->list)
1173 {
1174 if (!string_is_empty(db->content_database_path))
1175 dbstate->list = dir_list_new(
1176 db->content_database_path,
1177 "rdb", false,
1178 db->show_hidden_files,
1179 false, false);
1180
1181 /* If the scan path matches a database path exactly then
1182 * save time by only processing that database. */
1183 if (dbstate->list && db->is_directory)
1184 {
1185 size_t i;
1186 char *dirname = NULL;
1187
1188 if (!string_is_empty(db->fullpath))
1189 dirname = find_last_slash(db->fullpath) + 1;
1190
1191 if (!string_is_empty(dirname))
1192 {
1193 for (i = 0; i < dbstate->list->size; i++)
1194 {
1195 const char *data = dbstate->list->elems[i].data;
1196 char *dbname = NULL;
1197 bool strmatch = false;
1198 char *dbpath = strdup(data);
1199
1200 path_remove_extension(dbpath);
1201
1202 dbname = find_last_slash(dbpath) + 1;
1203 strmatch = strcasecmp(dbname, dirname) == 0;
1204
1205 free(dbpath);
1206
1207 if (strmatch)
1208 {
1209 struct string_list *single_list = string_list_new();
1210 string_list_append(single_list,
1211 data,
1212 dbstate->list->elems[i].attr);
1213 dir_list_free(dbstate->list);
1214 dbstate->list = single_list;
1215 break;
1216 }
1217 }
1218 }
1219 }
1220 }
1221 dbinfo->status = DATABASE_STATUS_ITERATE_START;
1222 break;
1223 case DATABASE_STATUS_ITERATE_START:
1224 name = database_info_get_current_element_name(dbinfo);
1225 task_database_cleanup_state(dbstate);
1226 dbstate->list_index = 0;
1227 dbstate->entry_index = 0;
1228 task_database_iterate_start(task, dbinfo, name);
1229 break;
1230 case DATABASE_STATUS_ITERATE:
1231 {
1232 bool path_contains_compressed_file = false;
1233 const char *name =
1234 database_info_get_current_element_name(dbinfo);
1235 if (!name)
1236 goto task_finished;
1237
1238 path_contains_compressed_file = path_contains_compressed_file(name);
1239 if (path_contains_compressed_file)
1240 if (dbinfo->type == DATABASE_TYPE_ITERATE)
1241 dbinfo->type = DATABASE_TYPE_ITERATE_ARCHIVE;
1242
1243 if (task_database_iterate(db, name, dbstate, dbinfo,
1244 path_contains_compressed_file) == 0)
1245 {
1246 dbinfo->status = DATABASE_STATUS_ITERATE_NEXT;
1247 dbinfo->type = DATABASE_TYPE_ITERATE;
1248 }
1249 }
1250 break;
1251 case DATABASE_STATUS_ITERATE_NEXT:
1252 dbinfo->list_ptr++;
1253
1254 if (dbinfo->list_ptr < dbinfo->list->size)
1255 {
1256 dbinfo->status = DATABASE_STATUS_ITERATE_START;
1257 dbinfo->type = DATABASE_TYPE_ITERATE;
1258 }
1259 else
1260 {
1261 const char *msg = NULL;
1262 if (db->is_directory)
1263 msg = msg_hash_to_str(MSG_SCANNING_OF_DIRECTORY_FINISHED);
1264 else
1265 msg = msg_hash_to_str(MSG_SCANNING_OF_FILE_FINISHED);
1266 #ifdef RARCH_INTERNAL
1267 task_free_title(task);
1268 task_set_title(task, strdup(msg));
1269 task_set_progress(task, 100);
1270 ui_companion_driver_notify_refresh();
1271 #else
1272 fprintf(stderr, "msg: %s\n", msg);
1273 #endif
1274 goto task_finished;
1275 }
1276 break;
1277 default:
1278 case DATABASE_STATUS_FREE:
1279 case DATABASE_STATUS_NONE:
1280 goto task_finished;
1281 }
1282
1283 return;
1284 task_finished:
1285 if (task)
1286 task_set_finished(task, true);
1287
1288 if (dbstate)
1289 {
1290 if (dbstate->list)
1291 dir_list_free(dbstate->list);
1292 }
1293
1294 if (db)
1295 {
1296 if (!string_is_empty(db->playlist_directory))
1297 free(db->playlist_directory);
1298 if (!string_is_empty(db->content_database_path))
1299 free(db->content_database_path);
1300 if (!string_is_empty(db->fullpath))
1301 free(db->fullpath);
1302 if (db->state.buf)
1303 free(db->state.buf);
1304
1305 if (db->handle)
1306 database_info_free(db->handle);
1307 free(db);
1308 }
1309
1310 if (dbinfo)
1311 free(dbinfo);
1312 }
1313
1314 #ifdef RARCH_INTERNAL
task_database_progress_cb(retro_task_t * task)1315 static void task_database_progress_cb(retro_task_t *task)
1316 {
1317 if (task)
1318 video_display_server_set_window_progress(task->progress,
1319 task->finished);
1320 }
1321 #endif
1322
task_push_dbscan(const char * playlist_directory,const char * content_database,const char * fullpath,bool directory,bool db_dir_show_hidden_files,retro_task_callback_t cb)1323 bool task_push_dbscan(
1324 const char *playlist_directory,
1325 const char *content_database,
1326 const char *fullpath,
1327 bool directory,
1328 bool db_dir_show_hidden_files,
1329 retro_task_callback_t cb)
1330 {
1331 retro_task_t *t = task_init();
1332 #ifdef RARCH_INTERNAL
1333 settings_t *settings = config_get_ptr();
1334 #endif
1335 db_handle_t *db = (db_handle_t*)calloc(1, sizeof(db_handle_t));
1336
1337 if (!t || !db)
1338 goto error;
1339
1340 t->handler = task_database_handler;
1341 t->state = db;
1342 t->callback = cb;
1343 t->title = strdup(msg_hash_to_str(
1344 MSG_PREPARING_FOR_CONTENT_SCAN));
1345 t->alternative_look = true;
1346
1347 #ifdef RARCH_INTERNAL
1348 t->progress_cb = task_database_progress_cb;
1349 db->scan_without_core_match = settings->bools.scan_without_core_match;
1350 db->playlist_config.capacity = COLLECTION_SIZE;
1351 db->playlist_config.old_format = settings->bools.playlist_use_old_format;
1352 db->playlist_config.compress = settings->bools.playlist_compression;
1353 db->playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match;
1354 playlist_config_set_base_content_directory(&db->playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL);
1355 #else
1356 db->playlist_config.capacity = COLLECTION_SIZE;
1357 db->playlist_config.old_format = false;
1358 db->playlist_config.compress = false;
1359 db->playlist_config.fuzzy_archive_match = false;
1360 playlist_config_set_base_content_directory(&db->playlist_config, NULL);
1361 #endif
1362 db->show_hidden_files = db_dir_show_hidden_files;
1363 db->is_directory = directory;
1364 db->fullpath = strdup(fullpath);
1365 db->playlist_directory = strdup(playlist_directory);
1366 db->content_database_path = strdup(content_database);
1367
1368 task_queue_push(t);
1369
1370 return true;
1371
1372 error:
1373 if (t)
1374 free(t);
1375 if (db)
1376 free(db);
1377 return false;
1378 }
1379