mirror of
https://github.com/libretro/RetroArch.git
synced 2025-02-02 06:53:39 +00:00
Merge pull request #9816 from jdgleaver/manual-scan-arcade-dat
(Manual Content Scanner) Enable automatic naming of arcade content via DAT files
This commit is contained in:
commit
ed20c14f23
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
"<Unspecified>"
|
||||
)
|
||||
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"
|
||||
|
455
libretro-common/formats/logiqx_dat/logiqx_dat.c
Normal file
455
libretro-common/formats/logiqx_dat/logiqx_dat.c
Normal file
@ -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 <file/file_path.h>
|
||||
#include <string/stdstring.h>
|
||||
#include <formats/rxml.h>
|
||||
|
||||
#include <formats/logiqx_dat.h>
|
||||
|
||||
/* 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;
|
||||
}
|
@ -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;
|
||||
|
107
libretro-common/include/formats/logiqx_dat.h
Normal file
107
libretro-common/include/formats/logiqx_dat.h
Normal file
@ -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 <retro_common_api.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
|
||||
#include <boolean.h>
|
||||
|
||||
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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <boolean.h>
|
||||
|
||||
#include <lists/string_list.h>
|
||||
#include <formats/logiqx_dat.h>
|
||||
|
||||
#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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <string/stdstring.h>
|
||||
#include <lists/string_list.h>
|
||||
#include <file/file_path.h>
|
||||
#include <formats/logiqx_dat.h>
|
||||
|
||||
#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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user