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