1 /*  RetroArch - A frontend for libretro.
2  *  Copyright (C) 2010-2014 - Hans-Kristian Arntzen
3  *  Copyright (C) 2011-2017 - Daniel De Matteis
4  *  Copyright (C) 2016-2019 - Brad Parker
5  *
6  *  RetroArch is free software: you can redistribute it and/or modify it under the terms
7  *  of the GNU General Public License as published by the Free Software Found-
8  *  ation, either version 3 of the License, or (at your option) any later version.
9  *
10  *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
11  *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12  *  PURPOSE.  See the GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License along with RetroArch.
15  *  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <stdio.h>
19 #include <stdint.h>
20 
21 #include <compat/strl.h>
22 #include <retro_endianness.h>
23 #include <file/file_path.h>
24 #include <lists/string_list.h>
25 #include <lists/dir_list.h>
26 #include <string/stdstring.h>
27 
28 #include "libretro-db/libretrodb.h"
29 
30 #include "core_info.h"
31 #include "database_info.h"
32 
database_info_build_query_enum(char * s,size_t len,enum database_query_type type,const char * path)33 int database_info_build_query_enum(char *s, size_t len,
34       enum database_query_type type,
35       const char *path)
36 {
37    bool add_quotes = true;
38    bool add_glob   = false;
39 
40    s[0]            = '{';
41    s[1]            = '\'';
42    s[2]            = '\0';
43 
44    switch (type)
45    {
46       case DATABASE_QUERY_ENTRY:
47          strlcat(s, "name", len);
48          break;
49       case DATABASE_QUERY_ENTRY_PUBLISHER:
50          strlcat(s, "publisher", len);
51          break;
52       case DATABASE_QUERY_ENTRY_DEVELOPER:
53          strlcat(s, "developer", len);
54          add_glob   = true;
55          add_quotes = false;
56          break;
57       case DATABASE_QUERY_ENTRY_ORIGIN:
58          strlcat(s, "origin", len);
59          break;
60       case DATABASE_QUERY_ENTRY_FRANCHISE:
61          strlcat(s, "franchise", len);
62          break;
63       case DATABASE_QUERY_ENTRY_RATING:
64          strlcat(s, "esrb_rating", len);
65          break;
66       case DATABASE_QUERY_ENTRY_BBFC_RATING:
67          strlcat(s, "bbfc_rating", len);
68          break;
69       case DATABASE_QUERY_ENTRY_ELSPA_RATING:
70          strlcat(s, "elspa_rating", len);
71          break;
72       case DATABASE_QUERY_ENTRY_ESRB_RATING:
73          strlcat(s, "esrb_rating", len);
74          break;
75       case DATABASE_QUERY_ENTRY_PEGI_RATING:
76          strlcat(s, "pegi_rating", len);
77          break;
78       case DATABASE_QUERY_ENTRY_CERO_RATING:
79          strlcat(s, "cero_rating", len);
80          break;
81       case DATABASE_QUERY_ENTRY_ENHANCEMENT_HW:
82          strlcat(s, "enhancement_hw", len);
83          break;
84       case DATABASE_QUERY_ENTRY_EDGE_MAGAZINE_RATING:
85          strlcat(s, "edge_rating", len);
86          add_quotes = false;
87          break;
88       case DATABASE_QUERY_ENTRY_EDGE_MAGAZINE_ISSUE:
89          strlcat(s, "edge_issue", len);
90          add_quotes = false;
91          break;
92       case DATABASE_QUERY_ENTRY_FAMITSU_MAGAZINE_RATING:
93          strlcat(s, "famitsu_rating", len);
94          add_quotes = false;
95          break;
96       case DATABASE_QUERY_ENTRY_RELEASEDATE_MONTH:
97          strlcat(s, "releasemonth", len);
98          add_quotes = false;
99          break;
100       case DATABASE_QUERY_ENTRY_RELEASEDATE_YEAR:
101          strlcat(s, "releaseyear", len);
102          add_quotes = false;
103          break;
104       case DATABASE_QUERY_ENTRY_MAX_USERS:
105          strlcat(s, "users", len);
106          add_quotes = false;
107          break;
108       case DATABASE_QUERY_NONE:
109          break;
110    }
111 
112    strlcat(s, "':", len);
113    if (add_glob)
114       strlcat(s, "glob('*", len);
115    if (add_quotes)
116       strlcat(s, "\"", len);
117    strlcat(s, path, len);
118    if (add_glob)
119       strlcat(s, "*')", len);
120    if (add_quotes)
121       strlcat(s, "\"", len);
122 
123    strlcat(s, "}", len);
124 
125    return 0;
126 }
127 
128 /*
129  * NOTE: Allocates memory, it is the caller's responsibility to free the
130  * memory after it is no longer required.
131  */
bin_to_hex_alloc(const uint8_t * data,size_t len)132 char *bin_to_hex_alloc(const uint8_t *data, size_t len)
133 {
134    size_t i;
135    char *ret = (char*)malloc(len * 2 + 1);
136 
137    if (len && !ret)
138       return NULL;
139 
140    for (i = 0; i < len; i++)
141       snprintf(ret+i * 2, 3, "%02X", data[i]);
142    return ret;
143 }
144 
database_cursor_iterate(libretrodb_cursor_t * cur,database_info_t * db_info)145 static int database_cursor_iterate(libretrodb_cursor_t *cur,
146       database_info_t *db_info)
147 {
148    unsigned i;
149    struct rmsgpack_dom_value item;
150    const char* str                = NULL;
151 
152    if (libretrodb_cursor_read_item(cur, &item) != 0)
153       return -1;
154 
155    if (item.type != RDT_MAP)
156    {
157       rmsgpack_dom_value_free(&item);
158       return 1;
159    }
160 
161    db_info->analog_supported       = -1;
162    db_info->rumble_supported       = -1;
163    db_info->coop_supported         = -1;
164 
165    for (i = 0; i < item.val.map.len; i++)
166    {
167       struct rmsgpack_dom_value *key = &item.val.map.items[i].key;
168       struct rmsgpack_dom_value *val = &item.val.map.items[i].value;
169       const char *val_string         = NULL;
170 
171       if (!key || !val)
172          continue;
173 
174       val_string                     = val->val.string.buff;
175       str                            = key->val.string.buff;
176 
177       if (string_is_equal(str, "publisher"))
178       {
179          if (!string_is_empty(val_string))
180             db_info->publisher = strdup(val_string);
181       }
182       else if (string_is_equal(str, "developer"))
183       {
184          if (!string_is_empty(val_string))
185             db_info->developer = string_split(val_string, "|");
186       }
187       else if (string_is_equal(str, "serial"))
188       {
189          if (!string_is_empty(val_string))
190             db_info->serial = strdup(val_string);
191       }
192       else if (string_is_equal(str, "rom_name"))
193       {
194          if (!string_is_empty(val_string))
195             db_info->rom_name = strdup(val_string);
196       }
197       else if (string_is_equal(str, "name"))
198       {
199          if (!string_is_empty(val_string))
200             db_info->name = strdup(val_string);
201       }
202       else if (string_is_equal(str, "description"))
203       {
204          if (!string_is_empty(val_string))
205             db_info->description = strdup(val_string);
206       }
207       else if (string_is_equal(str, "genre"))
208       {
209          if (!string_is_empty(val_string))
210             db_info->genre = strdup(val_string);
211       }
212       else if (string_is_equal(str, "origin"))
213       {
214          if (!string_is_empty(val_string))
215             db_info->origin = strdup(val_string);
216       }
217       else if (string_is_equal(str, "franchise"))
218       {
219          if (!string_is_empty(val_string))
220             db_info->franchise = strdup(val_string);
221       }
222       else if (string_ends_with_size(str, "_rating",
223                strlen(str), STRLEN_CONST("_rating")))
224       {
225          if (string_is_equal(str, "bbfc_rating"))
226          {
227             if (!string_is_empty(val_string))
228                db_info->bbfc_rating = strdup(val_string);
229          }
230          else if (string_is_equal(str, "esrb_rating"))
231          {
232             if (!string_is_empty(val_string))
233                db_info->esrb_rating = strdup(val_string);
234          }
235          else if (string_is_equal(str, "elspa_rating"))
236          {
237             if (!string_is_empty(val_string))
238                db_info->elspa_rating = strdup(val_string);
239          }
240          else if (string_is_equal(str, "cero_rating"))
241          {
242             if (!string_is_empty(val_string))
243                db_info->cero_rating          = strdup(val_string);
244          }
245          else if (string_is_equal(str, "pegi_rating"))
246          {
247             if (!string_is_empty(val_string))
248                db_info->pegi_rating          = strdup(val_string);
249          }
250          else if (string_is_equal(str, "edge_rating"))
251             db_info->edge_magazine_rating    = (unsigned)val->val.uint_;
252          else if (string_is_equal(str, "famitsu_rating"))
253             db_info->famitsu_magazine_rating = (unsigned)val->val.uint_;
254          else if (string_is_equal(str, "tgdb_rating"))
255             db_info->tgdb_rating             = (unsigned)val->val.uint_;
256       }
257       else if (string_is_equal(str, "enhancement_hw"))
258       {
259          if (!string_is_empty(val_string))
260             db_info->enhancement_hw       = strdup(val_string);
261       }
262       else if (string_is_equal(str, "edge_review"))
263       {
264          if (!string_is_empty(val_string))
265             db_info->edge_magazine_review = strdup(val_string);
266       }
267       else if (string_is_equal(str, "edge_issue"))
268          db_info->edge_magazine_issue     = (unsigned)val->val.uint_;
269       else if (string_is_equal(str, "users"))
270          db_info->max_users               = (unsigned)val->val.uint_;
271       else if (string_is_equal(str, "releasemonth"))
272          db_info->releasemonth            = (unsigned)val->val.uint_;
273       else if (string_is_equal(str, "releaseyear"))
274          db_info->releaseyear             = (unsigned)val->val.uint_;
275       else if (string_is_equal(str, "rumble"))
276          db_info->rumble_supported        = (int)val->val.uint_;
277       else if (string_is_equal(str, "coop"))
278          db_info->coop_supported          = (int)val->val.uint_;
279       else if (string_is_equal(str, "analog"))
280          db_info->analog_supported        = (int)val->val.uint_;
281       else if (string_is_equal(str, "size"))
282          db_info->size                    = (unsigned)val->val.uint_;
283       else if (string_is_equal(str, "crc"))
284          db_info->crc32 = swap_if_little32(
285                *(uint32_t*)val->val.binary.buff);
286       else if (string_is_equal(str, "sha1"))
287          db_info->sha1 = bin_to_hex_alloc(
288                (uint8_t*)val->val.binary.buff, val->val.binary.len);
289       else if (string_is_equal(str, "md5"))
290          db_info->md5 = bin_to_hex_alloc(
291                (uint8_t*)val->val.binary.buff, val->val.binary.len);
292    }
293 
294    rmsgpack_dom_value_free(&item);
295 
296    return 0;
297 }
298 
database_cursor_open(libretrodb_t * db,libretrodb_cursor_t * cur,const char * path,const char * query)299 static int database_cursor_open(libretrodb_t *db,
300       libretrodb_cursor_t *cur, const char *path, const char *query)
301 {
302    const char *error     = NULL;
303    libretrodb_query_t *q = NULL;
304 
305    if ((libretrodb_open(path, db)) != 0)
306       return -1;
307 
308    if (query)
309       q = (libretrodb_query_t*)libretrodb_query_compile(db, query,
310       strlen(query), &error);
311 
312    if (error)
313       goto error;
314    if ((libretrodb_cursor_open(db, cur, q)) != 0)
315       goto error;
316 
317    if (q)
318       libretrodb_query_free(q);
319 
320    return 0;
321 
322 error:
323    if (q)
324       libretrodb_query_free(q);
325    libretrodb_close(db);
326 
327    return -1;
328 }
329 
database_cursor_close(libretrodb_t * db,libretrodb_cursor_t * cur)330 static int database_cursor_close(libretrodb_t *db, libretrodb_cursor_t *cur)
331 {
332    libretrodb_cursor_close(cur);
333    libretrodb_close(db);
334 
335    return 0;
336 }
337 
type_is_prioritized(const char * path)338 static bool type_is_prioritized(const char *path)
339 {
340    const char *ext = path_get_extension(path);
341    if (string_is_equal_noncase(ext, "cue"))
342       return true;
343    if (string_is_equal_noncase(ext, "gdi"))
344       return true;
345    return false;
346 }
347 
dir_entry_compare(const void * left,const void * right)348 static int dir_entry_compare(const void *left, const void *right)
349 {
350    const struct string_list_elem *le = (const struct string_list_elem*)left;
351    const struct string_list_elem *re = (const struct string_list_elem*)right;
352    bool                            l = type_is_prioritized(le->data);
353    bool                            r = type_is_prioritized(re->data);
354 
355    return (int) r - (int) l;
356 }
357 
dir_list_prioritize(struct string_list * list)358 static void dir_list_prioritize(struct string_list *list)
359 {
360    qsort(list->elems, list->size, sizeof(*list->elems), dir_entry_compare);
361 }
362 
database_info_dir_init(const char * dir,enum database_type type,retro_task_t * task,bool show_hidden_files)363 database_info_handle_t *database_info_dir_init(const char *dir,
364       enum database_type type, retro_task_t *task,
365       bool show_hidden_files)
366 {
367    core_info_list_t *core_info_list = NULL;
368    struct string_list       *list   = NULL;
369    database_info_handle_t     *db   = (database_info_handle_t*)
370       malloc(sizeof(*db));
371 
372    if (!db)
373       return NULL;
374 
375    core_info_get_list(&core_info_list);
376 
377    list = dir_list_new(dir, core_info_list ? core_info_list->all_ext : NULL,
378          false, show_hidden_files,
379          false, true);
380 
381    if (!list)
382    {
383       free(db);
384       return NULL;
385    }
386 
387    dir_list_prioritize(list);
388 
389    db->status             = DATABASE_STATUS_ITERATE;
390    db->type               = type;
391    db->list_ptr           = 0;
392    db->list               = list;
393 
394    return db;
395 }
396 
database_info_file_init(const char * path,enum database_type type,retro_task_t * task)397 database_info_handle_t *database_info_file_init(const char *path,
398       enum database_type type, retro_task_t *task)
399 {
400    union string_list_elem_attr attr;
401    struct string_list        *list  = NULL;
402    database_info_handle_t      *db  = (database_info_handle_t*)
403       malloc(sizeof(*db));
404 
405    if (!db)
406       return NULL;
407 
408    attr.i             = 0;
409 
410    list               = string_list_new();
411 
412    if (!list)
413    {
414       free(db);
415       return NULL;
416    }
417 
418    string_list_append(list, path, attr);
419 
420    db->status             = DATABASE_STATUS_ITERATE;
421    db->type               = type;
422    db->list_ptr           = 0;
423    db->list               = list;
424 
425    return db;
426 }
427 
database_info_free(database_info_handle_t * db)428 void database_info_free(database_info_handle_t *db)
429 {
430    if (!db)
431       return;
432 
433    string_list_free(db->list);
434 }
435 
database_info_list_new(const char * rdb_path,const char * query)436 database_info_list_t *database_info_list_new(
437       const char *rdb_path, const char *query)
438 {
439    int ret                                  = 0;
440    unsigned k                               = 0;
441    database_info_t *database_info           = NULL;
442    database_info_list_t *database_info_list = NULL;
443    libretrodb_t *db                         = libretrodb_new();
444    libretrodb_cursor_t *cur                 = libretrodb_cursor_new();
445 
446    if (!db || !cur)
447       goto end;
448 
449    if ((database_cursor_open(db, cur, rdb_path, query) != 0))
450       goto end;
451 
452    database_info_list = (database_info_list_t*)
453       malloc(sizeof(*database_info_list));
454 
455    if (!database_info_list)
456       goto end;
457 
458    database_info_list->count  = 0;
459    database_info_list->list   = NULL;
460 
461    while (ret != -1)
462    {
463       database_info_t db_info = {0};
464       ret = database_cursor_iterate(cur, &db_info);
465 
466       if (ret == 0)
467       {
468          database_info_t *db_ptr  = NULL;
469          database_info_t *new_ptr = (database_info_t*)
470             realloc(database_info, (k+1) * sizeof(database_info_t));
471 
472          if (!new_ptr)
473          {
474             if (db_info.bbfc_rating)
475                free(db_info.bbfc_rating);
476             if (db_info.cero_rating)
477                free(db_info.cero_rating);
478             if (db_info.description)
479                free(db_info.description);
480             if (db_info.edge_magazine_review)
481                free(db_info.edge_magazine_review);
482             if (db_info.elspa_rating)
483                free(db_info.elspa_rating);
484             if (db_info.enhancement_hw)
485                free(db_info.enhancement_hw);
486             if (db_info.esrb_rating)
487                free(db_info.esrb_rating);
488             if (db_info.franchise)
489                free(db_info.franchise);
490             if (db_info.genre)
491                free(db_info.genre);
492             if (db_info.name)
493                free(db_info.name);
494             if (db_info.origin)
495                free(db_info.origin);
496             if (db_info.pegi_rating)
497                free(db_info.pegi_rating);
498             if (db_info.publisher)
499                free(db_info.publisher);
500             if (db_info.rom_name)
501                free(db_info.rom_name);
502             if (db_info.serial)
503                free(db_info.serial);
504             if (db_info.md5)
505                free(db_info.md5);
506             if (db_info.sha1)
507                free(db_info.sha1);
508             database_info_list_free(database_info_list);
509             free(database_info);
510             free(database_info_list);
511             database_info_list = NULL;
512             goto end;
513          }
514 
515          database_info = new_ptr;
516          db_ptr        = &database_info[k];
517 
518          memcpy(db_ptr, &db_info, sizeof(*db_ptr));
519 
520          k++;
521       }
522    }
523 
524    database_info_list->list  = database_info;
525    database_info_list->count = k;
526 
527 end:
528    if (db)
529    {
530       database_cursor_close(db, cur);
531       libretrodb_free(db);
532    }
533    if (cur)
534       libretrodb_cursor_free(cur);
535 
536    return database_info_list;
537 }
538 
database_info_list_free(database_info_list_t * database_info_list)539 void database_info_list_free(database_info_list_t *database_info_list)
540 {
541    size_t i;
542 
543    if (!database_info_list)
544       return;
545 
546    for (i = 0; i < database_info_list->count; i++)
547    {
548       database_info_t *info = &database_info_list->list[i];
549 
550       if (info->name)
551          free(info->name);
552       if (info->rom_name)
553          free(info->rom_name);
554       if (info->serial)
555          free(info->serial);
556       if (info->genre)
557          free(info->genre);
558       if (info->description)
559          free(info->description);
560       if (info->publisher)
561          free(info->publisher);
562       if (info->developer)
563          string_list_free(info->developer);
564       info->developer = NULL;
565       if (info->origin)
566          free(info->origin);
567       if (info->franchise)
568          free(info->franchise);
569       if (info->edge_magazine_review)
570          free(info->edge_magazine_review);
571 
572       if (info->cero_rating)
573          free(info->cero_rating);
574       if (info->pegi_rating)
575          free(info->pegi_rating);
576       if (info->enhancement_hw)
577          free(info->enhancement_hw);
578       if (info->elspa_rating)
579          free(info->elspa_rating);
580       if (info->esrb_rating)
581          free(info->esrb_rating);
582       if (info->bbfc_rating)
583          free(info->bbfc_rating);
584       if (info->sha1)
585          free(info->sha1);
586       if (info->md5)
587          free(info->md5);
588    }
589 
590    free(database_info_list->list);
591 }
592