diff --git a/Makefile.common b/Makefile.common index f611336b3b..e4eb1e2812 100644 --- a/Makefile.common +++ b/Makefile.common @@ -852,9 +852,6 @@ ifeq ($(HAVE_VIDEO_LAYOUT), 1) gfx/video_layout/internal.o \ gfx/video_layout/scope.o \ gfx/video_layout/load.o - OBJ += \ - $(LIBRETRO_COMM_DIR)/formats/xml/rxml.o \ - deps/yxml/yxml.o endif ifeq ($(HAVE_STB_FONT), 1) @@ -989,6 +986,12 @@ ifeq ($(HAVE_WAYLAND), 1) endif +# XML +OBJ += \ + $(LIBRETRO_COMM_DIR)/formats/xml/rxml.o \ + $(LIBRETRO_COMM_DIR)/formats/logiqx_dat/logiqx_dat.o \ + deps/yxml/yxml.o + #Input ifeq ($(HAVE_DINPUT), 1) diff --git a/griffin/griffin.c b/griffin/griffin.c index 74308b86ff..154967e8f5 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -1484,10 +1484,9 @@ DEPENDENCIES /*============================================================ XML ============================================================ */ -#ifdef HAVE_VIDEO_LAYOUT #include "../libretro-common/formats/xml/rxml.c" +#include "../libretro-common/formats/logiqx_dat/logiqx_dat.c" #include "../deps/yxml/yxml.c" -#endif /*============================================================ AUDIO UTILS diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 9a4c923e79..5c300977fd 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -2129,6 +2129,8 @@ MSG_HASH(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_FILE_EXTS, "manual_content_scan_file_exts") MSG_HASH(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_SEARCH_ARCHIVES, "manual_content_scan_search_archives") +MSG_HASH(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_DAT_FILE, + "manual_content_scan_dat_file") MSG_HASH(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_OVERWRITE, "manual_content_scan_overwrite") MSG_HASH(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_START, diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 5b46804010..58ae9522d1 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -10108,6 +10108,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_SEARCH_ARCHIVES, "When enabled, archive files (.zip, .7z, etc.) will be searched for valid/supported content. May have a significant impact on scan performance." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_DAT_FILE, + "Arcade DAT File" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_DAT_FILE, + "Select a Logiqx or MAME List XML DAT file to enable automatic naming of scanned arcade content (MAME, FinalBurn Neo, etc.)." + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_OVERWRITE, "Overwrite Existing Playlist" @@ -10136,6 +10144,18 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_CORE_NAME_DETECT, "" ) +MSG_HASH( + MSG_MANUAL_CONTENT_SCAN_DAT_FILE_INVALID, + "Invalid arcade DAT file selected" + ) +MSG_HASH( + MSG_MANUAL_CONTENT_SCAN_DAT_FILE_TOO_LARGE, + "Selected arcade DAT file is too large (insufficient free memory)" + ) +MSG_HASH( + MSG_MANUAL_CONTENT_SCAN_DAT_FILE_LOAD_ERROR, + "Failed to load arcade DAT file (invalid format?)" + ) MSG_HASH( MSG_MANUAL_CONTENT_SCAN_INVALID_CONFIG, "Invalid manual scan configuration" diff --git a/libretro-common/formats/logiqx_dat/logiqx_dat.c b/libretro-common/formats/logiqx_dat/logiqx_dat.c new file mode 100644 index 0000000000..c3f716d1f9 --- /dev/null +++ b/libretro-common/formats/logiqx_dat/logiqx_dat.c @@ -0,0 +1,455 @@ +/* Copyright (C) 2010-2019 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (logiqx_dat.c). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include + +#include + +/* Holds all internal DAT file data */ +struct logiqx_dat +{ + rxml_document_t *data; + rxml_node_t *current_node; +}; + +/* List of HTML formatting codes that must + * be replaced when parsing XML data */ +const char *logiqx_dat_html_code_list[][2] = { + {"&", "&"}, + {"'", "'"}, + {">", ">"}, + {"<", "<"}, + {""", "\""} +}; + +#define LOGIQX_DAT_HTML_CODE_LIST_SIZE 5 + +/* Validation */ + +/* Performs rudimentary validation of the specified + * Logiqx XML DAT file path (not rigorous - just + * enough to prevent obvious errors). + * Also provides access to file size (DAT files can + * be very large, so it is useful to have this information + * on hand - i.e. so we can check that the system has + * enough free memory to load the file). */ +bool logiqx_dat_path_is_valid(const char *path, uint64_t *file_size) +{ + const char *file_ext = NULL; + int32_t file_size_int; + + if (string_is_empty(path)) + return false; + + /* Check file extension */ + file_ext = path_get_extension(path); + + if (string_is_empty(file_ext)) + return false; + + if (!string_is_equal_noncase(file_ext, "dat") && + !string_is_equal_noncase(file_ext, "xml")) + return false; + + /* Ensure file exists */ + if (!path_is_valid(path)) + return false; + + /* Get file size */ + file_size_int = path_get_size(path); + + if (file_size_int <= 0) + return false; + + if (file_size) + *file_size = (uint64_t)file_size_int; + + return true; +} + +/* File initialisation/de-initialisation */ + +/* Loads specified Logiqx XML DAT file from disk. + * Returned logiqx_dat_t object must be free'd using + * logiqx_dat_free(). + * Returns NULL if file is invalid or a read error + * occurs. */ +logiqx_dat_t *logiqx_dat_init(const char *path) +{ + logiqx_dat_t *dat_file = NULL; + rxml_node_t *root_node = NULL; + + /* Check file path */ + if (!logiqx_dat_path_is_valid(path, NULL)) + goto error; + + /* Create logiqx_dat_t object */ + dat_file = (logiqx_dat_t*)calloc(1, sizeof(*dat_file)); + + if (!dat_file) + goto error; + + /* Read file from disk */ + dat_file->data = rxml_load_document(path); + + if (!dat_file->data) + goto error; + + /* Ensure root node has the correct name */ + root_node = rxml_root_node(dat_file->data); + + if (!root_node) + goto error; + + if (string_is_empty(root_node->name)) + goto error; + + /* > Logiqx XML uses: 'datafile' + * > MAME List XML uses: 'mame' + * > MAME 'Software List' uses: 'softwarelist' */ + if (!string_is_equal(root_node->name, "datafile") && + !string_is_equal(root_node->name, "mame") && + !string_is_equal(root_node->name, "softwarelist")) + goto error; + + /* Get pointer to initial child node */ + dat_file->current_node = root_node->children; + + if (!dat_file->current_node) + goto error; + + /* All is well - return logiqx_dat_t object */ + return dat_file; + +error: + logiqx_dat_free(dat_file); + return NULL; +} + +/* Frees specified DAT file */ +void logiqx_dat_free(logiqx_dat_t *dat_file) +{ + if (!dat_file) + return; + + dat_file->current_node = NULL; + + if (dat_file->data) + { + rxml_free_document(dat_file->data); + dat_file->data = NULL; + } + + free(dat_file); + dat_file = NULL; +} + +/* Game information access */ + +/* Returns true if specified node is a 'game' entry */ +static bool logiqx_dat_is_game_node(rxml_node_t *node) +{ + const char *node_name = NULL; + + if (!node) + return false; + + /* Check node name */ + node_name = node->name; + + if (string_is_empty(node_name)) + return false; + + /* > Logiqx XML uses: 'game' + * > MAME List XML uses: 'machine' + * > MAME 'Software List' uses: 'software' */ + return string_is_equal(node_name, "game") || + string_is_equal(node_name, "machine") || + string_is_equal(node_name, "software"); +} + +/* Returns true if specified node is a game + * node containing information for a game with + * the specified name */ +static bool logiqx_dat_game_node_matches_name( + rxml_node_t *node, const char *game_name) +{ + const char *node_game_name = NULL; + + if (!logiqx_dat_is_game_node(node) || + string_is_empty(game_name)) + return false; + + /* Get 'name' attribute of XML node */ + node_game_name = rxml_node_attrib(node, "name"); + + if (string_is_empty(node_game_name)) + return false; + + return string_is_equal(node_game_name, game_name); +} + +/* The XML element data strings returned from + * DAT files are very 'messy'. This function + * removes all cruft, replaces formatting strings + * and copies the result (if valid) to 'str' */ +static void logiqx_dat_sanitise_element_data( + const char *data, char *str, size_t len) +{ + char sanitised_data[PATH_MAX_LENGTH]; + size_t i; + + sanitised_data[0] = '\0'; + + if (string_is_empty(data)) + return; + + strlcpy(sanitised_data, data, sizeof(sanitised_data)); + + /* Element data includes leading/trailing + * newline characters - trim them away */ + string_trim_whitespace(sanitised_data); + + if (string_is_empty(sanitised_data)) + return; + + /* XML has a number of special characters that + * are handled using a HTML formatting codes. + * All of these have to be replaced... + * & -> & + * ' -> ' + * > -> > + * < -> < + * " -> " + */ + for (i = 0; i < LOGIQX_DAT_HTML_CODE_LIST_SIZE; i++) + { + const char *find_string = logiqx_dat_html_code_list[i][0]; + const char *replace_string = logiqx_dat_html_code_list[i][1]; + + /* string_replace_substring() is expensive + * > only invoke if element string contains + * HTML code */ + if (strstr(sanitised_data, find_string)) + { + char *tmp = string_replace_substring( + sanitised_data, find_string, replace_string); + + if (!string_is_empty(tmp)) + strlcpy(sanitised_data, tmp, sizeof(sanitised_data)); + + if (tmp) + free(tmp); + } + } + + if (string_is_empty(sanitised_data)) + return; + + /* All is well - can copy result */ + strlcpy(str, sanitised_data, len); +} + +/* Extracts game information from specified node. + * Returns false if node is invalid */ +static bool logiqx_dat_parse_game_node( + rxml_node_t *node, logiqx_dat_game_info_t *game_info) +{ + const char *game_name = NULL; + const char *is_bios = NULL; + const char *is_runnable = NULL; + rxml_node_t *info_node = NULL; + bool description_found = false; + bool year_found = false; + bool manufacturer_found = false; + + if (!logiqx_dat_is_game_node(node)) + return false; + + if (!game_info) + return false; + + /* Initialise logiqx_dat_game_info_t object */ + game_info->name[0] = '\0'; + game_info->description[0] = '\0'; + game_info->year[0] = '\0'; + game_info->manufacturer[0] = '\0'; + game_info->is_bios = false; + game_info->is_runnable = true; + + /* Get game name */ + game_name = rxml_node_attrib(node, "name"); + + if (!string_is_empty(game_name)) + strlcpy(game_info->name, game_name, sizeof(game_info->name)); + + /* Get 'is bios' status */ + is_bios = rxml_node_attrib(node, "isbios"); + + if (!string_is_empty(is_bios)) + game_info->is_bios = string_is_equal(is_bios, "yes"); + + /* Get 'is runnable' status + * > Note: This attribute only exists in MAME List + * XML files, but there is no harm in checking for + * it generally. For normal Logiqx XML files, + * 'is runnable' is just the inverse of 'is bios' */ + is_runnable = rxml_node_attrib(node, "runnable"); + + if (!string_is_empty(is_runnable)) + game_info->is_runnable = string_is_equal(is_runnable, "yes"); + else + game_info->is_runnable = !game_info->is_bios; + + /* Loop over all game info nodes */ + for (info_node = node->children; info_node; info_node = info_node->next) + { + const char *info_node_name = info_node->name; + const char *info_node_data = info_node->data; + + if (string_is_empty(info_node_name)) + continue; + + /* Check description */ + if (string_is_equal(info_node_name, "description")) + { + logiqx_dat_sanitise_element_data( + info_node_data, game_info->description, + sizeof(game_info->description)); + description_found = true; + } + /* Check year */ + else if (string_is_equal(info_node_name, "year")) + { + logiqx_dat_sanitise_element_data( + info_node_data, game_info->year, + sizeof(game_info->year)); + year_found = true; + } + /* Check manufacturer */ + else if (string_is_equal(info_node_name, "manufacturer")) + { + logiqx_dat_sanitise_element_data( + info_node_data, game_info->manufacturer, + sizeof(game_info->manufacturer)); + manufacturer_found = true; + } + + /* If all required entries have been found, + * can end loop */ + if (description_found && year_found && manufacturer_found) + break; + } + + return true; +} + +/* Sets/resets internal node pointer to the first + * entry in the DAT file */ +void logiqx_dat_set_first(logiqx_dat_t *dat_file) +{ + rxml_node_t *root_node = NULL; + + if (!dat_file) + return; + + if (!dat_file->data) + return; + + /* Get root node */ + root_node = rxml_root_node(dat_file->data); + + if (!root_node) + { + dat_file->current_node = NULL; + return; + } + + /* Get pointer to initial child node */ + dat_file->current_node = root_node->children; +} + +/* Fetches game information for the current entry + * in the DAT file and increments the internal node + * pointer. + * Returns false if the end of the DAT file has been + * reached (in which case 'game_info' will be invalid) */ +bool logiqx_dat_get_next( + logiqx_dat_t *dat_file, logiqx_dat_game_info_t *game_info) +{ + if (!dat_file || !game_info) + return false; + + if (!dat_file->data) + return false; + + while (dat_file->current_node) + { + rxml_node_t *current_node = dat_file->current_node; + + /* Whatever happens, internal node pointer must + * be 'incremented' */ + dat_file->current_node = dat_file->current_node->next; + + /* If this is a game node, extract info + * and return */ + if (logiqx_dat_is_game_node(current_node)) + return logiqx_dat_parse_game_node(current_node, game_info); + } + + return false; +} + +/* Fetches information for the specified game. + * Returns false if game does not exist, or arguments + * are invalid. */ +bool logiqx_dat_search( + logiqx_dat_t *dat_file, const char *game_name, + logiqx_dat_game_info_t *game_info) +{ + rxml_node_t *root_node = NULL; + rxml_node_t *game_node = NULL; + + if (!dat_file || !game_info || string_is_empty(game_name)) + return false; + + if (!dat_file->data) + return false; + + /* Get root node */ + root_node = rxml_root_node(dat_file->data); + + if (!root_node) + return false; + + /* Loop over all child nodes of the DAT file */ + for (game_node = root_node->children; game_node; game_node = game_node->next) + { + /* If this is the requested game, fetch info and return */ + if (logiqx_dat_game_node_matches_name(game_node, game_name)) + return logiqx_dat_parse_game_node(game_node, game_info); + } + + return false; +} diff --git a/libretro-common/formats/xml/rxml.c b/libretro-common/formats/xml/rxml.c index 4513e45312..6b4ed36b5a 100644 --- a/libretro-common/formats/xml/rxml.c +++ b/libretro-common/formats/xml/rxml.c @@ -66,19 +66,15 @@ static void rxml_free_node(struct rxml_node *node) for (attrib_node_head = node->attrib; attrib_node_head; ) { - struct rxml_attrib_node *next_attrib = NULL; + struct rxml_attrib_node *next_attrib = + (struct rxml_attrib_node*)attrib_node_head->next; - next_attrib = (struct rxml_attrib_node*)attrib_node_head->next; - - if (next_attrib) - { - if (attrib_node_head->attrib) - free(attrib_node_head->attrib); - if (attrib_node_head->value) - free(attrib_node_head->value); - if (attrib_node_head) - free(attrib_node_head); - } + if (attrib_node_head->attrib) + free(attrib_node_head->attrib); + if (attrib_node_head->value) + free(attrib_node_head->value); + if (attrib_node_head) + free(attrib_node_head); attrib_node_head = next_attrib; } @@ -161,6 +157,7 @@ rxml_document_t *rxml_load_document_string(const char *str) case YXML_ELEMSTART: if (node) { + if (level > stack_i) { buf->stack[stack_i] = node; ++stack_i; @@ -177,7 +174,10 @@ rxml_document_t *rxml_load_document_string(const char *str) node = doc->root_node = (rxml_node_t*)calloc(1, sizeof(*node)); } + if (node->name) + free(node->name); node->name = strdup(x.elem); + attr = NULL; ++level; @@ -188,7 +188,25 @@ rxml_document_t *rxml_load_document_string(const char *str) if (valptr > buf->val) { *valptr = '\0'; - node->data = strdup(buf->val); + + /* Original code was broken here: + * > If an element ended on two successive + * iterations, on the second iteration + * the 'data' for the *previous* node would + * get overwritten + * > This effectively erased the data for the + * previous node, *and* caused a memory leak + * (due to the double strdup()) + * It seems the correct thing to do here is + * only copy the data if the current 'level' + * and 'stack index' are the same... */ + if (level == stack_i) + { + if (node->data) + free(node->data); + node->data = strdup(buf->val); + } + valptr = buf->val; } @@ -211,7 +229,10 @@ rxml_document_t *rxml_load_document_string(const char *str) else attr = node->attrib = (struct rxml_attrib_node*)calloc(1, sizeof(*attr)); + if (attr->attrib) + free(attr->attrib); attr->attrib = strdup(x.attr); + valptr = buf->val; break; @@ -225,7 +246,11 @@ rxml_document_t *rxml_load_document_string(const char *str) case YXML_ATTREND: if (valptr > buf->val) { *valptr = '\0'; + + if (attr->value) + free(attr->value); attr->value = strdup(buf->val); + valptr = buf->val; } break; diff --git a/libretro-common/include/formats/logiqx_dat.h b/libretro-common/include/formats/logiqx_dat.h new file mode 100644 index 0000000000..86c9badfb4 --- /dev/null +++ b/libretro-common/include/formats/logiqx_dat.h @@ -0,0 +1,107 @@ +/* Copyright (C) 2010-2019 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (logiqx_dat.h). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __LIBRETRO_SDK_FORMAT_LOGIQX_DAT_H__ +#define __LIBRETRO_SDK_FORMAT_LOGIQX_DAT_H__ + +#include +#include + +#include + +RETRO_BEGIN_DECLS + +/* Trivial handler for DAT files in Logiqx XML format + * (http://www.logiqx.com/). Provides bare minimum + * functionality - predominantly concerned with obtaining + * description text for specific arcade ROM images. + * + * Note: Also supports the following alternative DAT + * formats, since they are functionally identical to + * Logiqx XML (but with different element names): + * > MAME List XML + * > MAME 'Software List' */ + +/* Prevent direct access to logiqx_dat_t members */ +typedef struct logiqx_dat logiqx_dat_t; + +/* Holds all metadata for a single game entry + * in the DAT file (minimal at present - may be + * expanded with individual internal ROM data + * if required) */ +typedef struct +{ + char name[PATH_MAX_LENGTH]; + char description[PATH_MAX_LENGTH]; + char year[8]; + char manufacturer[128]; + bool is_bios; + bool is_runnable; +} logiqx_dat_game_info_t; + +/* Validation */ + +/* Performs rudimentary validation of the specified + * Logiqx XML DAT file path (not rigorous - just + * enough to prevent obvious errors). + * Also provides access to file size (DAT files can + * be very large, so it is useful to have this information + * on hand - i.e. so we can check that the system has + * enough free memory to load the file). */ +bool logiqx_dat_path_is_valid(const char *path, uint64_t *file_size); + +/* File initialisation/de-initialisation */ + +/* Loads specified Logiqx XML DAT file from disk. + * Returned logiqx_dat_t object must be free'd using + * logiqx_dat_free(). + * Returns NULL if file is invalid or a read error + * occurs. */ +logiqx_dat_t *logiqx_dat_init(const char *path); + +/* Frees specified DAT file */ +void logiqx_dat_free(logiqx_dat_t *dat_file); + +/* Game information access */ + +/* Sets/resets internal node pointer to the first + * entry in the DAT file */ +void logiqx_dat_set_first(logiqx_dat_t *dat_file); + +/* Fetches game information for the current entry + * in the DAT file and increments the internal node + * pointer. + * Returns false if the end of the DAT file has been + * reached (in which case 'game_info' will be invalid) */ +bool logiqx_dat_get_next( + logiqx_dat_t *dat_file, logiqx_dat_game_info_t *game_info); + +/* Fetches information for the specified game. + * Returns false if game does not exist, or arguments + * are invalid. */ +bool logiqx_dat_search( + logiqx_dat_t *dat_file, const char *game_name, + logiqx_dat_game_info_t *game_info); + +RETRO_END_DECLS + +#endif diff --git a/libretro-common/include/formats/rxml.h b/libretro-common/include/formats/rxml.h index 06255524b7..e7dac569ab 100644 --- a/libretro-common/include/formats/rxml.h +++ b/libretro-common/include/formats/rxml.h @@ -64,4 +64,6 @@ struct rxml_node *rxml_root_node(rxml_document_t *doc); const char *rxml_node_attrib(struct rxml_node *node, const char *attrib); +RETRO_END_DECLS + #endif diff --git a/manual_content_scan.c b/manual_content_scan.c index ac571f854d..b5be9409ed 100644 --- a/manual_content_scan.c +++ b/manual_content_scan.c @@ -32,6 +32,8 @@ #include "core_info.h" #include "file_path_special.h" +#include "frontend/frontend_driver.h" + #include "manual_content_scan.h" /* Holds all configuration parameters associated @@ -46,6 +48,7 @@ typedef struct char core_path[PATH_MAX_LENGTH]; char file_exts_core[PATH_MAX_LENGTH]; char file_exts_custom[PATH_MAX_LENGTH]; + char dat_file_path[PATH_MAX_LENGTH]; enum manual_content_scan_system_name_type system_name_type; enum manual_content_scan_core_type core_type; bool search_archives; @@ -70,6 +73,7 @@ static scan_settings_t scan_settings = { "", /* core_path */ "", /* file_exts_core */ "", /* file_exts_custom */ + "", /* dat_file_path */ MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR, /* system_name_type */ MANUAL_CONTENT_SCAN_CORE_DETECT, /* core_type */ false, /* search_archives */ @@ -110,6 +114,20 @@ size_t manual_content_scan_get_file_exts_custom_size(void) return sizeof(scan_settings.file_exts_custom); } +/* Returns a pointer to the internal + * 'dat_file_path' string */ +char *manual_content_scan_get_dat_file_path_ptr(void) +{ + return scan_settings.dat_file_path; +} + +/* Returns size of the internal + * 'dat_file_path' string */ +size_t manual_content_scan_get_dat_file_path_size(void) +{ + return sizeof(scan_settings.dat_file_path); +} + /* Returns a pointer to the internal * 'search_archives' bool */ bool *manual_content_scan_get_search_archives_ptr(void) @@ -165,6 +183,61 @@ void manual_content_scan_scrub_file_exts_custom(void) manual_content_scan_scrub_file_exts(scan_settings.file_exts_custom); } +/* Checks 'dat_file_path' string and resets it + * if invalid */ +enum manual_content_scan_dat_file_path_status + manual_content_scan_validate_dat_file_path(void) +{ + enum manual_content_scan_dat_file_path_status dat_file_path_status = + MANUAL_CONTENT_SCAN_DAT_FILE_UNSET; + + /* Check if 'dat_file_path' has been set */ + if (!string_is_empty(scan_settings.dat_file_path)) + { + uint64_t file_size; + + /* Check if path itself is valid */ + if (logiqx_dat_path_is_valid(scan_settings.dat_file_path, &file_size)) + { + uint64_t free_memory = frontend_driver_get_free_memory(); + dat_file_path_status = MANUAL_CONTENT_SCAN_DAT_FILE_OK; + + /* DAT files can be *very* large... + * Try to enforce sane behaviour by requiring + * the system to have an amount of free memory + * at least twice the size of the DAT file... + * > Note that desktop (and probably mobile) + * platforms should always have enough memory + * for this - we're really only protecting the + * console ports here */ + if (free_memory > 0) + { + if (free_memory < (2 * file_size)) + dat_file_path_status = MANUAL_CONTENT_SCAN_DAT_FILE_TOO_LARGE; + } + /* This is an annoying condition - it means the + * current platform doesn't have a 'free_memory' + * implementation... + * Have to make some assumptions in this case: + * > Typically the lowest system RAM of a supported + * platform in 32MB + * > Propose that (2 * file_size) should be no more + * than 1/4 of this total RAM value */ + else if ((2 * file_size) > (8 * 1048576)) + dat_file_path_status = MANUAL_CONTENT_SCAN_DAT_FILE_TOO_LARGE; + } + else + dat_file_path_status = MANUAL_CONTENT_SCAN_DAT_FILE_INVALID; + } + + /* Reset 'dat_file_path' if status is anything other + * that 'OK' */ + if (dat_file_path_status != MANUAL_CONTENT_SCAN_DAT_FILE_OK) + scan_settings.dat_file_path[0] = '\0'; + + return dat_file_path_status; +} + /* Menu setters */ /* Sets content directory for next manual scan @@ -646,6 +719,7 @@ bool manual_content_scan_get_task_config(manual_content_scan_task_config_t *task task_config->core_name[0] = '\0'; task_config->core_path[0] = '\0'; task_config->file_exts[0] = '\0'; + task_config->dat_file_path[0] = '\0'; /* Get content directory */ if (string_is_empty(scan_settings.content_dir)) @@ -770,6 +844,18 @@ bool manual_content_scan_get_task_config(manual_content_scan_task_config_t *task if (!string_is_empty(task_config->file_exts)) string_replace_all_chars(task_config->file_exts, ' ', '|'); + /* Get DAT file path */ + if (!string_is_empty(scan_settings.dat_file_path)) + { + if (!logiqx_dat_path_is_valid(scan_settings.dat_file_path, NULL)) + return false; + + strlcpy( + task_config->dat_file_path, + scan_settings.dat_file_path, + sizeof(task_config->dat_file_path)); + } + /* Copy 'search inside archives' setting */ task_config->search_archives = scan_settings.search_archives; @@ -932,12 +1018,68 @@ error: return false; } +/* Extracts content 'label' (name) from content path + * > If a DAT file is specified and content is an + * archive, performs a lookup of content file name + * in an attempt to find a valid 'description' string. + * Returns false if specified content is invalid. */ +static bool manual_content_scan_get_playlist_content_label( + const char *content_path, logiqx_dat_t *dat_file, + char *content_label, size_t len) +{ + /* Sanity check */ + if (string_is_empty(content_path)) + return false; + + /* In most cases, content label is just the + * filename without extension */ + fill_short_pathname_representation( + content_label, content_path, len); + + if (string_is_empty(content_label)) + return false; + + /* Check if a DAT file has been specified */ + if (dat_file) + { + /* DAT files are only relevant for arcade + * content. We have no idea what kind of + * content we are dealing with here, but + * since arcade ROMs are always archives + * we can at least filter by file type... */ + if (path_is_compressed_file(content_path)) + { + logiqx_dat_game_info_t game_info; + + /* Search for current content + * > If content is not listed in DAT file, + * use existing filename without extension */ + if (logiqx_dat_search(dat_file, content_label, &game_info)) + { + /* BIOS files should always be skipped */ + if (game_info.is_bios) + return false; + + /* Only include 'runnable' content */ + if (!game_info.is_runnable) + return false; + + /* Copy game description */ + if (!string_is_empty(game_info.description)) + strlcpy(content_label, game_info.description, len); + } + } + } + + return true; +} + /* Adds specified content to playlist, if not already * present */ void manual_content_scan_add_content_to_playlist( manual_content_scan_task_config_t *task_config, playlist_t *playlist, const char *content_path, - int content_type) + int content_type, logiqx_dat_t *dat_file) { char playlist_content_path[PATH_MAX_LENGTH]; @@ -963,10 +1105,9 @@ void manual_content_scan_add_content_to_playlist( label[0] = '\0'; /* Get entry label */ - fill_short_pathname_representation( - label, playlist_content_path, sizeof(label)); - - if (string_is_empty(label)) + if (!manual_content_scan_get_playlist_content_label( + playlist_content_path, dat_file, + label, sizeof(label))) return; /* Configure playlist entry diff --git a/manual_content_scan.h b/manual_content_scan.h index 2b84e12efb..db52285e89 100644 --- a/manual_content_scan.h +++ b/manual_content_scan.h @@ -29,6 +29,7 @@ #include #include +#include #include "playlist.h" @@ -54,6 +55,16 @@ enum manual_content_scan_core_type MANUAL_CONTENT_SCAN_CORE_SET }; +/* Defines all possible return values for + * manual_content_scan_validate_dat_file_path() */ +enum manual_content_scan_dat_file_path_status +{ + MANUAL_CONTENT_SCAN_DAT_FILE_UNSET = 0, + MANUAL_CONTENT_SCAN_DAT_FILE_OK, + MANUAL_CONTENT_SCAN_DAT_FILE_INVALID, + MANUAL_CONTENT_SCAN_DAT_FILE_TOO_LARGE +}; + /* Holds all configuration parameters required * for a manual content scan task */ typedef struct @@ -65,6 +76,7 @@ typedef struct char core_name[PATH_MAX_LENGTH]; char core_path[PATH_MAX_LENGTH]; char file_exts[PATH_MAX_LENGTH]; + char dat_file_path[PATH_MAX_LENGTH]; bool core_set; bool search_archives; bool overwrite_playlist; @@ -96,6 +108,14 @@ char *manual_content_scan_get_file_exts_custom_ptr(void); * 'file_exts_custom' string */ size_t manual_content_scan_get_file_exts_custom_size(void); +/* Returns a pointer to the internal + * 'dat_file_path' string */ +char *manual_content_scan_get_dat_file_path_ptr(void); + +/* Returns size of the internal + * 'dat_file_path' string */ +size_t manual_content_scan_get_dat_file_path_size(void); + /* Returns a pointer to the internal * 'search_archives' bool */ bool *manual_content_scan_get_search_archives_ptr(void); @@ -115,6 +135,11 @@ void manual_content_scan_scrub_system_name_custom(void); * lower case */ void manual_content_scan_scrub_file_exts_custom(void); +/* Checks 'dat_file_path' string and resets it + * if invalid */ +enum manual_content_scan_dat_file_path_status + manual_content_scan_validate_dat_file_path(void); + /* Menu setters */ /* Sets content directory for next manual scan @@ -199,7 +224,7 @@ struct string_list *manual_content_scan_get_content_list(manual_content_scan_tas void manual_content_scan_add_content_to_playlist( manual_content_scan_task_config_t *task_config, playlist_t *playlist, const char *content_path, - int content_type); + int content_type, logiqx_dat_t *dat_file); RETRO_END_DECLS diff --git a/menu/cbs/menu_cbs_deferred_push.c b/menu/cbs/menu_cbs_deferred_push.c index e76bc9ce17..2b4c52263f 100644 --- a/menu/cbs/menu_cbs_deferred_push.c +++ b/menu/cbs/menu_cbs_deferred_push.c @@ -219,6 +219,7 @@ generic_deferred_push(deferred_push_switch_backlight_control, DISPLAYLIST_ #endif generic_deferred_push(deferred_push_manual_content_scan_list, DISPLAYLIST_MANUAL_CONTENT_SCAN_LIST) +generic_deferred_push(deferred_push_manual_content_scan_dat_file, DISPLAYLIST_MANUAL_CONTENT_SCAN_DAT_FILES) static int deferred_push_cursor_manager_list_deferred( menu_displaylist_info_t *info) @@ -1100,6 +1101,9 @@ static int menu_cbs_init_bind_deferred_push_compare_label( case MENU_ENUM_LABEL_DEFERRED_MANUAL_CONTENT_SCAN_LIST: BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_manual_content_scan_list); break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_DAT_FILE: + BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_manual_content_scan_dat_file); + break; default: return -1; } @@ -1316,6 +1320,9 @@ static int menu_cbs_init_bind_deferred_push_compare_label( case MENU_LABEL_DEFERRED_MANUAL_CONTENT_SCAN_LIST: BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_manual_content_scan_list); break; + case MENU_LABEL_MANUAL_CONTENT_SCAN_DAT_FILE: + BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_manual_content_scan_dat_file); + break; default: return -1; } diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index 5884f351ce..476aa8d1aa 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -116,7 +116,8 @@ enum ACTION_OK_SET_DIRECTORY, ACTION_OK_SHOW_WIMP, ACTION_OK_LOAD_CHEAT_FILE_APPEND, - ACTION_OK_LOAD_RGUI_MENU_THEME_PRESET + ACTION_OK_LOAD_RGUI_MENU_THEME_PRESET, + ACTION_OK_SET_MANUAL_CONTENT_SCAN_DAT_FILE }; enum @@ -638,6 +639,14 @@ int generic_action_ok_displaylist_push(const char *path, info_label = label; dl_type = DISPLAYLIST_FILE_BROWSER_SELECT_DIR; break; + case ACTION_OK_DL_MANUAL_CONTENT_SCAN_DAT_FILE: + filebrowser_clear_type(); + info.type = type; + info.directory_ptr = idx; + info_path = settings->paths.directory_menu_content; + info_label = label; + dl_type = DISPLAYLIST_FILE_BROWSER_SELECT_FILE; + break; case ACTION_OK_DL_REMAP_FILE: filebrowser_clear_type(); info.type = type; @@ -1706,6 +1715,10 @@ static int generic_action_ok(const char *path, ret = set_path_generic(menu_label, action_path); break; #endif + case ACTION_OK_SET_MANUAL_CONTENT_SCAN_DAT_FILE: + flush_char = msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_MANUAL_CONTENT_SCAN_LIST); + ret = set_path_generic(menu_label, action_path); + break; case ACTION_OK_SET_PATH: flush_type = MENU_SETTINGS; ret = set_path_generic(menu_label, action_path); @@ -1782,6 +1795,7 @@ default_action_ok_set(action_ok_shader_preset_load, ACTION_OK_LOAD_PRESET , default_action_ok_set(action_ok_shader_pass_load, ACTION_OK_LOAD_SHADER_PASS, MENU_ENUM_LABEL_SHADER_OPTIONS) #endif default_action_ok_set(action_ok_rgui_menu_theme_preset_load, ACTION_OK_LOAD_RGUI_MENU_THEME_PRESET, MENU_ENUM_LABEL_MENU_SETTINGS) +default_action_ok_set(action_ok_set_manual_content_scan_dat_file, ACTION_OK_SET_MANUAL_CONTENT_SCAN_DAT_FILE, MENU_ENUM_LABEL_DEFERRED_MANUAL_CONTENT_SCAN_LIST) static int action_ok_file_load(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) @@ -4738,6 +4752,7 @@ default_action_ok_func(action_ok_open_archive, ACTION_OK_DL_OPEN_ARCHIVE) default_action_ok_func(action_ok_rgui_menu_theme_preset, ACTION_OK_DL_RGUI_MENU_THEME_PRESET) default_action_ok_func(action_ok_pl_thumbnails_updater_list, ACTION_OK_DL_PL_THUMBNAILS_UPDATER_LIST) default_action_ok_func(action_ok_push_manual_content_scan_list, ACTION_OK_DL_MANUAL_CONTENT_SCAN_LIST) +default_action_ok_func(action_ok_manual_content_scan_dat_file, ACTION_OK_DL_MANUAL_CONTENT_SCAN_DAT_FILE) static int action_ok_open_uwp_permission_settings(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) @@ -6771,6 +6786,9 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_CORE_NAME: BIND_ACTION_OK(cbs, action_ok_manual_content_scan_core_name); break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_DAT_FILE: + BIND_ACTION_OK(cbs, action_ok_manual_content_scan_dat_file); + break; case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_START: BIND_ACTION_OK(cbs, action_ok_manual_content_scan_start); break; @@ -6908,6 +6926,9 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs, case MENU_LABEL_MANUAL_CONTENT_SCAN_CORE_NAME: BIND_ACTION_OK(cbs, action_ok_manual_content_scan_core_name); break; + case MENU_LABEL_MANUAL_CONTENT_SCAN_DAT_FILE: + BIND_ACTION_OK(cbs, action_ok_manual_content_scan_dat_file); + break; default: return -1; } @@ -7122,6 +7143,9 @@ static int menu_cbs_init_bind_ok_compare_type(menu_file_list_cbs_t *cbs, case FILE_TYPE_MANUAL_SCAN_DIRECTORY: BIND_ACTION_OK(cbs, action_ok_path_manual_scan_directory); break; + case FILE_TYPE_MANUAL_SCAN_DAT: + BIND_ACTION_OK(cbs, action_ok_set_manual_content_scan_dat_file); + break; case FILE_TYPE_CONFIG: BIND_ACTION_OK(cbs, action_ok_config_load); break; diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index cce80afe50..11f433c24d 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -726,6 +726,7 @@ default_sublabel_macro(action_bind_sublabel_manual_content_scan_system_name_cust default_sublabel_macro(action_bind_sublabel_manual_content_scan_core_name, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_CORE_NAME) default_sublabel_macro(action_bind_sublabel_manual_content_scan_file_exts, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_FILE_EXTS) default_sublabel_macro(action_bind_sublabel_manual_content_scan_search_archives, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_SEARCH_ARCHIVES) +default_sublabel_macro(action_bind_sublabel_manual_content_scan_dat_file, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_DAT_FILE) default_sublabel_macro(action_bind_sublabel_manual_content_scan_overwrite, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_OVERWRITE) default_sublabel_macro(action_bind_sublabel_manual_content_scan_start, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_START) @@ -3097,6 +3098,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_SEARCH_ARCHIVES: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_manual_content_scan_search_archives); break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_DAT_FILE: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_manual_content_scan_dat_file); + break; case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_OVERWRITE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_manual_content_scan_overwrite); break; diff --git a/menu/menu_cbs.h b/menu/menu_cbs.h index fb1f879522..e3f49752e9 100644 --- a/menu/menu_cbs.h +++ b/menu/menu_cbs.h @@ -171,7 +171,8 @@ enum ACTION_OK_DL_CONTENT_SETTINGS, ACTION_OK_DL_CDROM_INFO_DETAIL_LIST, ACTION_OK_DL_RGUI_MENU_THEME_PRESET, - ACTION_OK_DL_MANUAL_CONTENT_SCAN_LIST + ACTION_OK_DL_MANUAL_CONTENT_SCAN_LIST, + ACTION_OK_DL_MANUAL_CONTENT_SCAN_DAT_FILE }; /* Function callbacks */ diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index f2112d0762..4f7a0f62fe 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -3771,6 +3771,12 @@ static bool menu_displaylist_parse_manual_content_scan_list( false) == 0) count++; + /* Arcade DAT file */ + if (menu_displaylist_parse_settings_enum(info->list, + MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_DAT_FILE, PARSE_ONLY_PATH, + false) == 0) + count++; + /* Overwrite playlist */ if (menu_displaylist_parse_settings_enum(info->list, MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_OVERWRITE, PARSE_ONLY_BOOL, @@ -9009,6 +9015,7 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, case DISPLAYLIST_FONTS: case DISPLAYLIST_AUDIO_FILTERS: case DISPLAYLIST_CHEAT_FILES: + case DISPLAYLIST_MANUAL_CONTENT_SCAN_DAT_FILES: menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list); filebrowser_clear_type(); if (!string_is_empty(info->exts)) @@ -9055,6 +9062,10 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, info->type_default = FILE_TYPE_CHEAT; info->exts = strdup("cht"); break; + case DISPLAYLIST_MANUAL_CONTENT_SCAN_DAT_FILES: + info->type_default = FILE_TYPE_MANUAL_SCAN_DAT; + info->exts = strdup("dat|xml"); + break; default: break; } diff --git a/menu/menu_displaylist.h b/menu/menu_displaylist.h index 99b84c355b..29161089af 100644 --- a/menu/menu_displaylist.h +++ b/menu/menu_displaylist.h @@ -216,6 +216,7 @@ enum menu_displaylist_ctl_state DISPLAYLIST_SWITCH_CPU_PROFILE, #endif DISPLAYLIST_MANUAL_CONTENT_SCAN_LIST, + DISPLAYLIST_MANUAL_CONTENT_SCAN_DAT_FILES, DISPLAYLIST_PENDING_CLEAR }; diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 57abbb3758..18b8673b37 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -6695,6 +6695,31 @@ void general_write_handler(rarch_setting_t *setting) * string to lower case */ manual_content_scan_scrub_file_exts_custom(); break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_DAT_FILE: + /* Ensure that DAT file path is valid + * (cannot check file contents here - DAT + * files are too large to load in the UI + * thread - have to wait until the scan task + * is pushed) */ + switch (manual_content_scan_validate_dat_file_path()) + { + case MANUAL_CONTENT_SCAN_DAT_FILE_INVALID: + runloop_msg_queue_push( + msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_DAT_FILE_INVALID), + 1, 100, true, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + break; + case MANUAL_CONTENT_SCAN_DAT_FILE_TOO_LARGE: + runloop_msg_queue_push( + msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_DAT_FILE_TOO_LARGE), + 1, 100, true, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + break; + default: + /* No action required */ + break; + } + break; default: break; } @@ -16551,6 +16576,20 @@ static bool setting_append_list( general_read_handler, SD_FLAG_NONE); + CONFIG_PATH( + list, list_info, + manual_content_scan_get_dat_file_path_ptr(), + manual_content_scan_get_dat_file_path_size(), + MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_DAT_FILE, + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_DAT_FILE, + "", + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler); + menu_settings_list_current_add_values(list, list_info, "dat|xml"); + CONFIG_BOOL( list, list_info, manual_content_scan_get_overwrite_playlist_ptr(), diff --git a/menu/widgets/menu_filebrowser.c b/menu/widgets/menu_filebrowser.c index 1f165e4bd9..ce662144d1 100644 --- a/menu/widgets/menu_filebrowser.c +++ b/menu/widgets/menu_filebrowser.c @@ -109,6 +109,9 @@ void filebrowser_parse(menu_displaylist_info_t *info, unsigned type_data) (filter_ext && info) ? subsystem->roms[content_get_subsystem_rom_id()].valid_extensions : NULL, true, settings->bools.show_hidden_files, true, false); } + else if (info && (info->type_default == FILE_TYPE_MANUAL_SCAN_DAT)) + str_list = dir_list_new(path, + info->exts, true, settings->bools.show_hidden_files, false, false); else str_list = dir_list_new(path, (filter_ext && info) ? info->exts : NULL, diff --git a/msg_hash.h b/msg_hash.h index caf6fc2d14..0c5b05b8a1 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -152,6 +152,7 @@ enum msg_file_type FILE_TYPE_DOWNLOAD_PL_THUMBNAIL_CONTENT, FILE_TYPE_MANUAL_SCAN_DIRECTORY, + FILE_TYPE_MANUAL_SCAN_DAT, FILE_TYPE_LAST }; @@ -2676,6 +2677,7 @@ enum msg_hash_enums MENU_LABEL(MANUAL_CONTENT_SCAN_CORE_NAME), MENU_LABEL(MANUAL_CONTENT_SCAN_FILE_EXTS), MENU_LABEL(MANUAL_CONTENT_SCAN_SEARCH_ARCHIVES), + MENU_LABEL(MANUAL_CONTENT_SCAN_DAT_FILE), MENU_LABEL(MANUAL_CONTENT_SCAN_OVERWRITE), MENU_LABEL(MANUAL_CONTENT_SCAN_START), @@ -2684,6 +2686,9 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_CORE_NAME_DETECT, + MSG_MANUAL_CONTENT_SCAN_DAT_FILE_INVALID, + MSG_MANUAL_CONTENT_SCAN_DAT_FILE_TOO_LARGE, + MSG_MANUAL_CONTENT_SCAN_DAT_FILE_LOAD_ERROR, MSG_MANUAL_CONTENT_SCAN_INVALID_CONFIG, MSG_MANUAL_CONTENT_SCAN_INVALID_CONTENT, MSG_MANUAL_CONTENT_SCAN_START, @@ -2946,6 +2951,7 @@ enum msg_hash_enums #define MENU_LABEL_MANUAL_CONTENT_SCAN_DIR 0x6674149FU #define MENU_LABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME 0xA3EC34C5U #define MENU_LABEL_MANUAL_CONTENT_SCAN_CORE_NAME 0xD13B7849U +#define MENU_LABEL_MANUAL_CONTENT_SCAN_DAT_FILE 0x4CCE0EB8U /* Main menu */ #define MENU_LABEL_LOAD_CONTENT_LIST 0x5745de1fU diff --git a/tasks/task_manual_content_scan.c b/tasks/task_manual_content_scan.c index 8dc7c59ba4..9e8851ff15 100644 --- a/tasks/task_manual_content_scan.c +++ b/tasks/task_manual_content_scan.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "tasks_internal.h" @@ -51,6 +52,7 @@ typedef struct manual_scan_handle manual_content_scan_task_config_t *task_config; playlist_t *playlist; struct string_list *content_list; + logiqx_dat_t *dat_file; size_t list_size; size_t list_index; enum manual_scan_status status; @@ -80,6 +82,12 @@ static void free_manual_content_scan_handle(manual_scan_handle_t *manual_scan) manual_scan->content_list = NULL; } + if (manual_scan->dat_file) + { + logiqx_dat_free(manual_scan->dat_file); + manual_scan->dat_file = NULL; + } + free(manual_scan); manual_scan = NULL; } @@ -118,6 +126,22 @@ static void task_manual_content_scan_handler(retro_task_t *task) manual_scan->list_size = manual_scan->content_list->size; + /* Load DAT file, if required */ + if (!string_is_empty(manual_scan->task_config->dat_file_path)) + { + manual_scan->dat_file = + logiqx_dat_init(manual_scan->task_config->dat_file_path); + + if (!manual_scan->dat_file) + { + runloop_msg_queue_push( + msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_DAT_FILE_LOAD_ERROR), + 1, 100, true, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + goto task_finished; + } + } + /* Open playlist */ manual_scan->playlist = playlist_init( manual_scan->task_config->playlist_file, COLLECTION_SIZE); @@ -172,7 +196,7 @@ static void task_manual_content_scan_handler(retro_task_t *task) /* Add content to playlist */ manual_content_scan_add_content_to_playlist( manual_scan->task_config, manual_scan->playlist, - content_path, content_type); + content_path, content_type, manual_scan->dat_file); } /* Increment content index */ @@ -286,6 +310,7 @@ bool task_push_manual_content_scan(void) manual_scan->task_config = NULL; manual_scan->playlist = NULL; manual_scan->content_list = NULL; + manual_scan->dat_file = NULL; manual_scan->list_size = 0; manual_scan->list_index = 0; manual_scan->status = MANUAL_SCAN_BEGIN;