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