RetroArch/tasks/task_content.c
Eric Warmenhoven 7379d33801
tvOS Top Shelf (#16302)
This puts the History and Favorites playlists (up to five items each)
in the Top Shelf menu. In order for this to be enabled you must build
it yourself and change the app identifiers for the TV app and Top
Shelf extension, and add both of them to an app group.
2024-02-27 23:20:32 -08:00

3184 lines
102 KiB
C

/* RetroArch - A frontend for libretro.
* Copyright (C) 2011-2017 - Daniel De Matteis
* Copyright (C) 2016-2019 - Brad Parker
* Copyright (C) 2016-2019 - Andrés Suárez
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
/* TODO/FIXME - turn this into actual task */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <time.h>
#ifdef _WIN32
#ifdef _XBOX
#include <xtl.h>
#define setmode _setmode
#define INVALID_FILE_ATTRIBUTES -1
#else
#include <io.h>
#include <fcntl.h>
#include <windows.h>
#endif
#endif
#ifdef __WINRT__
#include <Fileapifromapp.h>
#include <uwp/uwp_func.h>
#endif
#ifdef HAVE_CONFIG_H
#include "../config.h"
#endif
#include <boolean.h>
#include <encodings/crc32.h>
#include <compat/strl.h>
#include <compat/posix_string.h>
#include <file/file_path.h>
#include <file/archive_file.h>
#include <streams/file_stream.h>
#include <string/stdstring.h>
#include <lists/string_list.h>
#include <lists/dir_list.h>
#include <vfs/vfs_implementation.h>
#include <array/rbuf.h>
#include <retro_miscellaneous.h>
#ifdef HAVE_MENU
#include "../menu/menu_driver.h"
#endif
#ifdef HAVE_GFX_WIDGETS
#include "../gfx/gfx_widgets.h"
#endif
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
#include "../menu/menu_shader.h"
#endif
#ifdef HAVE_CHEEVOS
#include "../cheevos/cheevos.h"
#endif
#include "task_content.h"
#include "tasks_internal.h"
#include "../command.h"
#include "../core_info.h"
#include "../content.h"
#include "../core.h"
#include "../configuration.h"
#include "../defaults.h"
#include "../dynamic.h"
#include "../file_path_special.h"
#include "../frontend/frontend.h"
#include "../msg_hash.h"
#include "../playlist.h"
#include "../paths.h"
#include "../retroarch.h"
#include "../runloop.h"
#include "../verbosity.h"
#ifdef HAVE_PRESENCE
#include "../network/presence.h"
#endif
#define MAX_ARGS 32
typedef struct content_stream content_stream_t;
typedef struct content_information_ctx content_information_ctx_t;
struct content_stream
{
const uint8_t *b;
size_t c;
uint32_t a;
uint32_t crc;
};
enum content_information_flags
{
CONTENT_INFO_FLAG_BLOCK_EXTRACT = (1 << 0),
CONTENT_INFO_FLAG_NEED_FULLPATH = (1 << 1),
CONTENT_INFO_FLAG_SET_SUPPORTS_NO_GAME_ENABLE = (1 << 2),
CONTENT_INFO_FLAG_IS_IPS_PREF = (1 << 3),
CONTENT_INFO_FLAG_IS_BPS_PREF = (1 << 4),
CONTENT_INFO_FLAG_IS_UPS_PREF = (1 << 5),
CONTENT_INFO_FLAG_PATCH_IS_BLOCKED = (1 << 6),
CONTENT_INFO_FLAG_BIOS_IS_MISSING = (1 << 7),
CONTENT_INFO_FLAG_CHECK_FW_BEFORE_LOADING = (1 << 8),
CONTENT_INFO_FLAG_IS_XDELTA_PREF = (1 << 9)
};
struct content_information_ctx
{
char *name_ips;
char *name_bps;
char *name_ups;
char *name_xdelta;
char *valid_extensions;
char *directory_cache;
char *directory_system;
struct
{
struct retro_subsystem_info *data;
unsigned size;
} subsystem;
uint16_t flags;
};
/*************************************/
/* Content file info functions START */
/*************************************/
static void content_file_override_free(
content_state_t *p_content)
{
size_t i;
if (!p_content->content_override_list)
return;
for (i = 0; i < RBUF_LEN(p_content->content_override_list); i++)
{
content_file_override_t *override =
&p_content->content_override_list[i];
if (override && override->ext)
free(override->ext);
}
RBUF_FREE(p_content->content_override_list);
}
/* Returns true if an override for content files
* of extension 'ext' has been set */
static bool content_file_override_get_ext(
content_state_t *p_content,
const char *ext,
const content_file_override_t **override)
{
size_t num_overrides;
size_t i;
if (p_content && !string_is_empty(ext))
{
if ((num_overrides = RBUF_LEN(p_content->content_override_list)) >= 1)
{
for (i = 0; i < num_overrides; i++)
{
content_file_override_t *content_override =
&p_content->content_override_list[i];
if (!content_override ||
!content_override->ext)
continue;
if (string_is_equal_noncase(
content_override->ext, ext))
{
if (override)
*override = content_override;
return true;
}
}
}
}
return false;
}
bool content_file_override_set(
const struct retro_system_content_info_override *overrides)
{
content_state_t *p_content = content_state_get_ptr();
size_t i;
if (!p_content || !overrides)
return false;
/* Free any existing override list */
content_file_override_free(p_content);
for (i = 0; overrides[i].extensions; i++)
{
struct string_list ext_list = {0};
size_t j;
/* Get list of extensions affected by override */
string_list_initialize(&ext_list);
if (!string_split_noalloc(&ext_list,
overrides[i].extensions, "|"))
{
string_list_deinitialize(&ext_list);
continue;
}
for (j = 0; j < ext_list.size; j++)
{
const char *ext = ext_list.elems[j].data;
content_file_override_t *override = NULL;
size_t num_entries;
/* Check whether extension has already been
* registered */
if (string_is_empty(ext) ||
content_file_override_get_ext(p_content, ext, NULL))
continue;
/* Add current override to the list */
num_entries = RBUF_LEN(p_content->content_override_list);
if (!RBUF_TRYFIT(p_content->content_override_list,
num_entries + 1))
{
string_list_deinitialize(&ext_list);
return false;
}
RBUF_RESIZE(p_content->content_override_list,
num_entries + 1);
RARCH_LOG("[Content Override]: File Extension: '%3s' - need_fullpath: %s, persistent_data: %s\n",
ext, overrides[i].need_fullpath ? "TRUE" : "FALSE",
overrides[i].persistent_data ? "TRUE" : "FALSE");
override = &p_content->content_override_list[num_entries];
override->ext = strdup(ext);
override->need_fullpath = overrides[i].need_fullpath;
override->persistent_data = overrides[i].persistent_data;
}
string_list_deinitialize(&ext_list);
}
return true;
}
/* Frees any content data that is not flagged
* as 'persistent'. Should be called after
* content_file_load() */
static void content_file_list_free_transient_data(
content_file_list_t *file_list)
{
size_t i;
if (!file_list)
return;
for (i = 0; i < file_list->size; i++)
{
content_file_info_t *file_info = &file_list->entries[i];
if (file_info->data &&
!file_info->persistent_data)
{
free((void*)file_info->data);
file_info->data = NULL;
file_info->data_size = 0;
}
}
}
static void content_file_list_free_entry(
content_file_info_t *file_info)
{
if (!file_info)
return;
if (file_info->full_path)
{
free(file_info->full_path);
file_info->full_path = NULL;
}
if (file_info->archive_path)
{
free(file_info->archive_path);
file_info->archive_path = NULL;
}
if (file_info->archive_file)
{
free(file_info->archive_file);
file_info->archive_file = NULL;
}
if (file_info->dir)
{
free(file_info->dir);
file_info->dir = NULL;
}
if (file_info->name)
{
free(file_info->name);
file_info->name = NULL;
}
if (file_info->ext)
{
free(file_info->ext);
file_info->ext = NULL;
}
if (file_info->meta)
{
free(file_info->meta);
file_info->meta = NULL;
}
if (file_info->data)
{
free((void*)file_info->data);
file_info->data = NULL;
}
file_info->data_size = 0;
file_info->file_in_archive = false;
file_info->persistent_data = false;
}
static void content_file_list_free(
content_file_list_t *file_list)
{
if (!file_list)
return;
if (file_list->entries)
{
size_t i;
for (i = 0; i < file_list->size; i++)
{
content_file_info_t *file_info = &file_list->entries[i];
content_file_list_free_entry(file_info);
}
free(file_list->entries);
}
if (file_list->temporary_files)
{
size_t i;
/* Remove any temporary content files from
* the file system */
for (i = 0; i < file_list->temporary_files->size; i++)
{
const char *path = file_list->temporary_files->elems[i].data;
if (string_is_empty(path))
continue;
RARCH_LOG("[Content]: %s: \"%s\".\n",
msg_hash_to_str(MSG_REMOVING_TEMPORARY_CONTENT_FILE),
path);
if (filestream_delete(path) != 0)
RARCH_ERR("[Content]: %s: \"%s\".\n",
msg_hash_to_str(MSG_FAILED_TO_REMOVE_TEMPORARY_FILE),
path);
}
string_list_free(file_list->temporary_files);
}
if (file_list->game_info)
free(file_list->game_info);
if (file_list->game_info_ext)
free(file_list->game_info_ext);
free(file_list);
}
static content_file_list_t *content_file_list_init(size_t size)
{
content_file_list_t *file_list = NULL;
if ((file_list = (content_file_list_t *)malloc(sizeof(*file_list))))
{
/* Set initial 'values' */
file_list->entries = NULL;
file_list->size = 0;
file_list->game_info = NULL;
file_list->game_info_ext = NULL;
file_list->temporary_files = string_list_new();
if (file_list->temporary_files)
{
/* Create entries list */
if ((file_list->entries = (content_file_info_t *)
calloc(size, sizeof(content_file_info_t))))
{
file_list->size = size;
/* Create retro_game_info object */
if ((file_list->game_info = (struct retro_game_info *)
calloc(size, sizeof(struct retro_game_info))))
{
/* Create retro_game_info_ext object */
if ((file_list->game_info_ext =
(struct retro_game_info_ext *)
calloc(size, sizeof(struct retro_game_info_ext))))
return file_list;
}
}
}
}
content_file_list_free(file_list);
return NULL;
}
/* Convenience function: Adds an entry to the
* temporary (i.e. extracted) content file list.
* Returns pointer to allocated char array. */
static const char *content_file_list_append_temporary(
content_file_list_t *file_list,
const char *path)
{
union string_list_elem_attr attr;
if (!file_list ||
string_is_empty(path))
return NULL;
attr.i = 0;
if (string_list_append(file_list->temporary_files,
path, attr))
return file_list->temporary_files->elems[
file_list->temporary_files->size - 1].data;
return NULL;
}
/* Note: Takes ownership of supplied 'data' buffer */
static bool content_file_list_set_info(
content_file_list_t *file_list,
const char *path,
void *data,
size_t data_size,
bool persistent_data,
size_t idx)
{
content_file_info_t *file_info = NULL;
struct retro_game_info *game_info = NULL;
struct retro_game_info_ext *game_info_ext = NULL;
if ( !file_list
|| (idx >= file_list->size))
return false;
if (!(file_info = &file_list->entries[idx]))
return false;
if (!(game_info = &file_list->game_info[idx]))
return false;
if (!(game_info_ext = &file_list->game_info_ext[idx]))
return false;
/* Clear any existing info */
content_file_list_free_entry(file_info);
game_info->path = NULL;
game_info->data = NULL;
game_info->size = 0;
game_info->meta = NULL;
game_info_ext->full_path = NULL;
game_info_ext->archive_path = NULL;
game_info_ext->archive_file = NULL;
game_info_ext->dir = NULL;
game_info_ext->name = NULL;
game_info_ext->ext = NULL;
game_info_ext->meta = NULL;
game_info_ext->data = NULL;
game_info_ext->size = 0;
game_info_ext->file_in_archive = false;
game_info_ext->persistent_data = false;
/* Assign data */
if (( data && !data_size) ||
(!data && data_size))
return false;
file_info->data = data;
file_info->data_size = data_size;
file_info->persistent_data = persistent_data;
/* Assign paths
* > There is some degree of redundant data
* here, but we need it in this format
* (persistent copies of each parameter)
* to minimise complications when passing
* extended path info to cores */
if (!string_is_empty(path))
{
char dir [PATH_MAX_LENGTH];
char name[NAME_MAX_LENGTH];
const char *archive_delim = NULL;
const char *ext = NULL;
/* 'Full' path - may point to a file
* inside an archive */
file_info->full_path = strdup(path);
/* File extension - may be used core-side
* to differentiate content types */
if ((ext = path_get_extension(path)))
{
file_info->ext = strdup(ext);
/* File extension is always presented
* core-side in lowercase format */
string_to_lower(file_info->ext);
}
/* Check whether path corresponds to a file
* inside an archive */
if ((archive_delim = path_get_archive_delim(path)))
{
char archive_path[PATH_MAX_LENGTH];
size_t len = 0;
/* Extract path of parent archive */
if ((len = (size_t)(1 + archive_delim - path))
>= PATH_MAX_LENGTH)
len = PATH_MAX_LENGTH;
strlcpy(archive_path, path, len * sizeof(char));
if (!string_is_empty(archive_path))
file_info->archive_path = strdup(archive_path);
/* Extract name of file in archive */
archive_delim++;
if (!string_is_empty(archive_delim))
file_info->archive_file = strdup(archive_delim);
/* Extract parent directory - may be used
* core-side as a reference point for searching
* related content (e.g. same file name, different
* extension) */
fill_pathname_parent_dir(dir, archive_path, sizeof(dir));
/* Extract 'canonical' name/id of content file -
* may be used core-side as a reference point for
* searching related content. For archived content,
* this is the basename of the archive file without
* extension */
fill_pathname_base(name, archive_path, sizeof(name));
path_remove_extension(name);
file_info->file_in_archive = true;
}
else
{
/* If path corresponds to a 'normal' file,
* just extract parent directory */
fill_pathname_parent_dir(dir, path, sizeof(dir));
/* For uncompressed content, 'canonical' name/id
* is the basename of the content file, without
* extension */
fill_pathname_base(name, path, sizeof(name));
path_remove_extension(name);
}
if (!string_is_empty(dir))
{
/* Remove any trailing slash */
char *last_slash = find_last_slash(dir);
if (last_slash && (last_slash[1] == '\0'))
*last_slash = '\0';
if (!string_is_empty(dir))
file_info->dir = strdup(dir);
}
if (!string_is_empty(name))
file_info->name = strdup(name);
}
/* Assign retro_game_info pointers */
game_info->path = file_info->full_path;
game_info->data = file_info->data;
game_info->size = file_info->data_size;
game_info->meta = file_info->meta;
/* Assign retro_game_info_ext pointers */
game_info_ext->full_path = file_info->full_path;
game_info_ext->archive_path = file_info->archive_path;
game_info_ext->archive_file = file_info->archive_file;
game_info_ext->dir = file_info->dir;
game_info_ext->name = file_info->name;
game_info_ext->ext = file_info->ext;
game_info_ext->meta = file_info->meta;
game_info_ext->data = file_info->data;
game_info_ext->size = file_info->data_size;
game_info_ext->file_in_archive = file_info->file_in_archive;
game_info_ext->persistent_data = file_info->persistent_data;
return true;
}
/***********************************/
/* Content file info functions END */
/***********************************/
/********************************/
/* Content file functions START */
/********************************/
#define CONTENT_FILE_ATTR_RESET(attr) (attr.i = 0)
#define CONTENT_FILE_ATTR_SET_BLOCK_EXTRACT(attr, block_extract) (attr.i |= ((block_extract) ? 1 : 0))
#define CONTENT_FILE_ATTR_SET_NEED_FULLPATH(attr, need_fullpath) (attr.i |= ((need_fullpath) ? 2 : 0))
#define CONTENT_FILE_ATTR_SET_REQUIRED(attr, required) (attr.i |= ((required) ? 4 : 0))
#define CONTENT_FILE_ATTR_SET_PERSISTENT(attr, persistent) (attr.i |= ((persistent) ? 8 : 0))
#define CONTENT_FILE_ATTR_GET_BLOCK_EXTRACT(attr) ((attr.i & 1) != 0)
#define CONTENT_FILE_ATTR_GET_NEED_FULLPATH(attr) ((attr.i & 2) != 0)
#define CONTENT_FILE_ATTR_GET_REQUIRED(attr) ((attr.i & 4) != 0)
#define CONTENT_FILE_ATTR_GET_PERSISTENT(attr) ((attr.i & 8) != 0)
/**
* content_file_load_into_memory:
* @content_path : path of the content file.
* @data : buffer into which the content file will be read.
* @data_size : size of the resultant content buffer.
*
* Reads the content file into memory. Also performs soft patching
* (see patch_content function) if soft patching has not been
* blocked by the user.
*
* Returns: true if successful, false on error.
**/
static bool content_file_load_into_memory(
content_information_ctx_t *content_ctx,
content_state_t *p_content,
const char *content_path,
bool content_compressed,
size_t idx,
enum rarch_content_type first_content_type,
uint8_t **data,
size_t *data_size)
{
uint8_t *content_data = NULL;
int64_t content_size = 0;
*data = NULL;
*data_size = 0;
RARCH_LOG("[Content]: %s: \"%s\".\n",
msg_hash_to_str(MSG_LOADING_CONTENT_FILE), content_path);
/* Read content from file into memory buffer */
#ifdef HAVE_COMPRESSION
if (content_compressed)
{
if (!file_archive_compressed_read(content_path,
(void**)&content_data, NULL, &content_size))
return false;
}
else
#endif
if (!filestream_read_file(content_path,
(void**)&content_data, &content_size))
return false;
if (content_size < 0)
return false;
/* First content file is significant: attempt to do
* soft patching, CRC checking, etc. */
if (idx == 0)
{
/* If we have a media type, ignore patches/CRC32
* calculation. */
if (first_content_type == RARCH_CONTENT_NONE)
{
bool has_patch = false;
#ifdef HAVE_PATCH
/* Attempt to apply a patch. */
if (!(content_ctx->flags & CONTENT_INFO_FLAG_PATCH_IS_BLOCKED))
has_patch = patch_content(
content_ctx->flags & CONTENT_INFO_FLAG_IS_IPS_PREF,
content_ctx->flags & CONTENT_INFO_FLAG_IS_BPS_PREF,
content_ctx->flags & CONTENT_INFO_FLAG_IS_UPS_PREF,
content_ctx->flags & CONTENT_INFO_FLAG_IS_XDELTA_PREF,
content_ctx->name_ips,
content_ctx->name_bps,
content_ctx->name_ups,
content_ctx->name_xdelta,
(uint8_t**)&content_data,
(void*)&content_size);
#endif
/* If content is compressed or a patch has been
* applied, must determine CRC value using the
* actual data buffer, since the content path
* cannot be used for this purpose...
* In all other cases, cache the content path
* and defer CRC calculation until the value is
* actually needed */
if (content_compressed || has_patch)
{
p_content->rom_crc = encoding_crc32(0, content_data,
(size_t)content_size);
RARCH_LOG("[Content]: CRC32: 0x%x.\n",
(unsigned)p_content->rom_crc);
}
else
{
/* We don't have the content ready inside a memory buffer,
so we have to read it from file later (deferred)
and then encode the CRC32 hash */
strlcpy(p_content->pending_rom_crc_path, content_path,
sizeof(p_content->pending_rom_crc_path));
p_content->flags |= CONTENT_ST_FLAG_PENDING_ROM_CRC;
}
}
else
p_content->rom_crc = 0;
}
*data = content_data;
*data_size = (size_t)content_size;
return true;
}
#ifdef HAVE_COMPRESSION
static bool content_file_extract_from_archive(
content_information_ctx_t *content_ctx,
content_state_t *p_content,
const char *valid_exts,
const char **content_path,
char **error_string)
{
const char *tmp_path_ptr = NULL;
char tmp_path[PATH_MAX_LENGTH];
char msg[1024];
tmp_path[0] = '\0';
msg[0] = '\0';
/* TODO/FIXME - localize */
RARCH_LOG("[Content]: Core requires uncompressed content - "
"extracting archive to temporary directory.\n");
/* Attempt to extract file */
if (!file_archive_extract_file(
*content_path, valid_exts,
string_is_empty(content_ctx->directory_cache) ?
NULL : content_ctx->directory_cache,
tmp_path, sizeof(tmp_path)))
{
snprintf(msg, sizeof(msg), "%s: \"%s\".\n",
msg_hash_to_str(MSG_FAILED_TO_EXTRACT_CONTENT_FROM_COMPRESSED_FILE),
*content_path);
*error_string = strdup(msg);
return false;
}
/* Add path of extracted file to temporary content
* list (so it can be deleted when deinitialising
* the core) */
if (!(tmp_path_ptr = content_file_list_append_temporary(
p_content->content_list, tmp_path)))
return false;
/* Update content path pointer */
*content_path = tmp_path_ptr;
/* TODO/FIXME - localize */
RARCH_LOG("[Content]: Content successfully extracted to: \"%s\".\n",
tmp_path);
return true;
}
#endif
static void content_file_get_path(
struct string_list *content,
size_t idx,
const char *valid_exts,
const char **path,
bool *path_is_compressed)
{
const char *content_path = content->elems[idx].data;
bool path_is_archive;
bool path_is_inside_archive;
*path = NULL;
*path_is_compressed = false;
if (string_is_empty(content_path))
return;
#ifdef HAVE_COMPRESSION
/* Check whether we are dealing with a
* compressed file */
path_is_archive = path_is_compressed_file(content_path);
path_is_inside_archive = path_contains_compressed_file(content_path);
*path_is_compressed = path_is_archive || path_is_inside_archive;
/* If extraction is permitted and content is a
* 'parent' archive file, must determine which
* internal file to load
* > file_archive_compressed_read() requires
* a 'complete' file path:
* <parent_archive>#<internal_file> */
if (!CONTENT_FILE_ATTR_GET_BLOCK_EXTRACT(content->elems[idx].attr) &&
path_is_archive &&
!path_is_inside_archive)
{
/* Get internal archive file list */
struct string_list *archive_list =
file_archive_get_file_list(content_path, valid_exts);
if (archive_list &&
(archive_list->size > 0))
{
const char *archive_file = NULL;
/* Ensure that list is sorted alphabetically */
if (archive_list->size > 1)
dir_list_sort(archive_list, true);
archive_file = archive_list->elems[0].data;
if (!string_is_empty(archive_file))
{
char info_path[PATH_MAX_LENGTH];
/* Build 'complete' archive file path */
size_t _len = strlcpy(info_path,
content_path, sizeof(info_path));
info_path[_len ] = '#';
info_path[_len+1] = '\0';
_len += 1;
strlcpy(info_path + _len, archive_file, sizeof(info_path) - _len);
/* Update 'content' string_list */
string_list_set(content, (unsigned)idx, info_path);
content_path = content->elems[idx].data;
string_list_free(archive_list);
}
}
}
#endif
*path = content_path;
}
static void content_file_apply_overrides(
content_state_t *p_content,
struct string_list *content,
size_t idx,
const char *path)
{
const content_file_override_t *override = NULL;
/* Check whether an override has been set
* for files of this type */
if (content_file_override_get_ext(p_content,
path_get_extension(path), &override))
{
/* Get existing attributes */
bool block_extract = CONTENT_FILE_ATTR_GET_BLOCK_EXTRACT(content->elems[idx].attr);
bool required = CONTENT_FILE_ATTR_GET_REQUIRED(content->elems[idx].attr);
bool persistent = CONTENT_FILE_ATTR_GET_PERSISTENT(content->elems[idx].attr);
CONTENT_FILE_ATTR_RESET(content->elems[idx].attr);
/* Apply updates
* > Note that 'persistent' attribute must not
* be set false by an override if it is already
* true (frontend may require persistence for
* other purposes, e.g. runahead) */
CONTENT_FILE_ATTR_SET_BLOCK_EXTRACT(content->elems[idx].attr,
block_extract);
CONTENT_FILE_ATTR_SET_NEED_FULLPATH(content->elems[idx].attr,
override->need_fullpath);
CONTENT_FILE_ATTR_SET_REQUIRED(content->elems[idx].attr,
required);
CONTENT_FILE_ATTR_SET_PERSISTENT(content->elems[idx].attr,
(persistent || override->persistent_data));
}
}
/**
* content_file_load:
* @special : subsystem of content to be loaded. Can be NULL.
*
* Load content file (for libretro core).
*
* Returns : true if successful, otherwise false.
**/
static bool content_file_load(
content_state_t *p_content,
struct string_list *content,
content_information_ctx_t *content_ctx,
enum msg_hash_enums *error_enum,
char **error_string,
const struct retro_subsystem_info *special)
{
size_t i;
char msg[1024];
retro_ctx_load_content_info_t load_info;
bool used_vfs_fallback_copy = false;
#ifdef __WINRT__
rarch_system_info_t *sys_info = &runloop_state_get_ptr()->system;
#endif
enum rarch_content_type first_content_type = RARCH_CONTENT_NONE;
msg[0] = '\0';
for (i = 0; i < content->size; i++)
{
const char *content_path = NULL;
uint8_t *content_data = NULL;
size_t content_size = 0;
const char *valid_exts = special
? special->roms[i].valid_extensions
: content_ctx->valid_extensions;
bool content_compressed = false;
/* Get content path */
content_file_get_path(content, i, valid_exts,
&content_path, &content_compressed);
/* If content is missing and core requires content,
* return an error */
if (string_is_empty(content_path))
{
if (CONTENT_FILE_ATTR_GET_REQUIRED(content->elems[i].attr))
{
*error_enum = MSG_ERROR_LIBRETRO_CORE_REQUIRES_CONTENT;
return false;
}
}
else
{
/* If this is the first item of content,
* get content type */
if (i == 0)
first_content_type = path_is_media_type(content_path);
/* Apply any file-type-specific content
* handling overrides */
if (p_content->content_override_list)
content_file_apply_overrides(p_content, content, i, content_path);
/* If core does not require 'fullpath', load
* the content into memory */
if (!CONTENT_FILE_ATTR_GET_NEED_FULLPATH(content->elems[i].attr))
{
if (!content_file_load_into_memory(
content_ctx, p_content, content_path,
content_compressed, i, first_content_type,
&content_data, &content_size))
{
snprintf(msg, sizeof(msg), "%s \"%s\"\n",
msg_hash_to_str(MSG_COULD_NOT_READ_CONTENT_FILE),
content_path);
*error_string = strdup(msg);
return false;
}
}
else
{
#ifdef HAVE_COMPRESSION
/* If this is compressed content and need_fullpath
* is true, extract it to a temporary file */
if (content_compressed &&
!CONTENT_FILE_ATTR_GET_BLOCK_EXTRACT(content->elems[i].attr) &&
!content_file_extract_from_archive(content_ctx, p_content,
valid_exts, &content_path, error_string))
return false;
#endif
#ifdef __WINRT__
/* TODO: When support for the 'actual' VFS is added,
* there will need to be some more logic here */
if ( !sys_info->supports_vfs
&& !is_path_accessible_using_standard_io(content_path))
{
/* Try to copy ACL to file first. If successful, this should mean that cores using standard I/O can still access them
* It would be better to set the ACL to allow full access for all application packages. However,
* this is substantially easier than writing out new functions to do this
* Copy ACL from localstate
* I am genuinely really proud of these work arounds
*/
wchar_t wcontent_path[MAX_PATH];
mbstowcs(wcontent_path, content_path, MAX_PATH);
uwp_set_acl(wcontent_path, L"S-1-15-2-1");
if (!is_path_accessible_using_standard_io(content_path))
{
wchar_t wnew_path[MAX_PATH];
/* Fallback to a file copy into an accessible directory */
char new_basedir[PATH_MAX_LENGTH];
char new_path[PATH_MAX_LENGTH];
RARCH_LOG("[Content]: Core does not support VFS"
" - copying to cache directory.\n");
if (!string_is_empty(content_ctx->directory_cache))
strlcpy(new_basedir, content_ctx->directory_cache,
sizeof(new_basedir));
else
new_basedir[0] = '\0';
if ( string_is_empty (new_basedir)
|| !path_is_directory(new_basedir)
|| !is_path_accessible_using_standard_io(new_basedir))
{
size_t _len;
DWORD basedir_attribs;
RARCH_WARN("[Content]: Tried copying to cache directory, "
"but cache directory was not set or found. "
"Setting cache directory to root of writable app directory...\n");
_len = strlcpy(new_basedir, uwp_dir_data, sizeof(new_basedir));
strlcpy(new_basedir + _len,
"VFSCACHE\\",
sizeof(new_basedir) - _len);
basedir_attribs = GetFileAttributes(new_basedir);
if ( (basedir_attribs == INVALID_FILE_ATTRIBUTES)
|| (!(basedir_attribs & FILE_ATTRIBUTE_DIRECTORY)))
{
if (!CreateDirectoryA(new_basedir, NULL))
strlcpy(new_basedir, uwp_dir_data, sizeof(new_basedir));
}
}
fill_pathname_join_special(new_path, new_basedir,
path_basename(content_path), sizeof(new_path));
mbstowcs(wnew_path, new_path, MAX_PATH);
/* TODO: This may fail on very large files...
* but copying large files is not a good idea anyway
* (This disclaimer is out dated but I don't want to remove it)*/
if (!CopyFileFromAppW(wcontent_path, wnew_path, false))
{
/* TODO/FIXME - localize */
snprintf(msg, sizeof(msg), "%s \"%s\". (during copy read or write)\n",
msg_hash_to_str(MSG_COULD_NOT_READ_CONTENT_FILE),
content_path);
*error_string = strdup(msg);
return false;
}
content_path = content_file_list_append_temporary(
p_content->content_list, new_path);
used_vfs_fallback_copy = true;
}
}
#endif
RARCH_LOG("[Content]: %s\n", msg_hash_to_str(
MSG_CONTENT_LOADING_SKIPPED_IMPLEMENTATION_WILL_DO_IT));
/* First content file is significant: need to
* perform CRC calculation, but defer this
* until value is used */
if (i == 0)
{
/* If we have a media type, ignore CRC32 calculation. */
if (first_content_type == RARCH_CONTENT_NONE)
{
strlcpy(p_content->pending_rom_crc_path, content_path,
sizeof(p_content->pending_rom_crc_path));
p_content->flags |= CONTENT_ST_FLAG_PENDING_ROM_CRC;
}
else
p_content->rom_crc = 0;
}
}
}
/* Add current entry to content file list */
if (!content_file_list_set_info(
p_content->content_list,
content_path, content_data, content_size,
CONTENT_FILE_ATTR_GET_PERSISTENT(content->elems[i].attr), i))
{
RARCH_LOG("[Content]: Failed to process content file: \"%s\".\n", content_path);
if (content_data)
free((void*)content_data);
*error_enum = MSG_FAILED_TO_LOAD_CONTENT;
return false;
}
}
/* Load content into core */
load_info.content = content;
load_info.special = special;
load_info.info = p_content->content_list->game_info;
if (!core_load_game(&load_info))
{
/* This is probably going to fail on multifile ROMs etc.
* so give a visible explanation of what is likely wrong */
if (used_vfs_fallback_copy)
*error_enum = MSG_ERROR_LIBRETRO_CORE_REQUIRES_VFS;
else
*error_enum = MSG_FAILED_TO_LOAD_CONTENT;
return false;
}
#ifdef HAVE_CHEEVOS
if (!special)
{
const char *first_content_path =
p_content->content_list->entries[0].full_path;
if (!string_is_empty(first_content_path))
{
if (first_content_type == RARCH_CONTENT_NONE)
{
rcheevos_load(p_content->content_list->game_info);
return true;
}
}
}
rcheevos_pause_hardcore();
#endif
return true;
}
static const struct retro_subsystem_info *content_file_init_subsystem(
const struct retro_subsystem_info *subsystem_data,
size_t subsystem_current_count,
enum msg_hash_enums *error_enum,
char **error_string,
bool *ret)
{
struct string_list *subsystem = path_get_subsystem_list();
const struct retro_subsystem_info *special = libretro_find_subsystem_info(
subsystem_data, (unsigned)subsystem_current_count,
path_get(RARCH_PATH_SUBSYSTEM));
char msg[1024];
msg[0] = '\0';
if (!special)
{
/* TODO/FIXME - localize */
snprintf(msg, sizeof(msg),
"Failed to find subsystem \"%s\" in libretro implementation.\n",
path_get(RARCH_PATH_SUBSYSTEM));
*error_string = strdup(msg);
goto error;
}
if (special->num_roms)
{
if (!subsystem)
{
*error_enum = MSG_ERROR_LIBRETRO_CORE_REQUIRES_SPECIAL_CONTENT;
goto error;
}
if (special->num_roms != subsystem->size)
{
/* TODO/FIXME - localize */
snprintf(msg, sizeof(msg),
"Libretro core requires %u content files for "
"subsystem \"%s\", but %u content files were provided.\n",
special->num_roms, special->desc,
(unsigned)subsystem->size);
*error_string = strdup(msg);
goto error;
}
}
else if (subsystem && subsystem->size)
{
/* TODO/FIXME - localize */
snprintf(msg, sizeof(msg),
"Libretro core takes no content for subsystem \"%s\", "
"but %u content files were provided.\n",
special->desc,
(unsigned)subsystem->size);
*error_string = strdup(msg);
goto error;
}
*ret = true;
return special;
error:
*ret = false;
return NULL;
}
static void content_file_set_attributes(
struct string_list *content,
const struct retro_subsystem_info *special,
content_information_ctx_t *content_ctx,
char **error_string)
{
struct string_list *subsystem = path_get_subsystem_list();
if (!path_is_empty(RARCH_PATH_SUBSYSTEM) && special)
{
size_t i;
for (i = 0; i < subsystem->size; i++)
{
union string_list_elem_attr attr;
CONTENT_FILE_ATTR_RESET(attr);
CONTENT_FILE_ATTR_SET_BLOCK_EXTRACT(attr, special->roms[i].block_extract);
CONTENT_FILE_ATTR_SET_NEED_FULLPATH(attr, special->roms[i].need_fullpath);
CONTENT_FILE_ATTR_SET_REQUIRED(attr, special->roms[i].required);
string_list_append(content, subsystem->elems[i].data, attr);
}
}
else
{
union string_list_elem_attr attr;
const char *content_path = path_get(RARCH_PATH_CONTENT);
uint8_t flags = content_get_flags();
CONTENT_FILE_ATTR_RESET(attr);
CONTENT_FILE_ATTR_SET_BLOCK_EXTRACT(attr, content_ctx->flags &
CONTENT_INFO_FLAG_BLOCK_EXTRACT);
CONTENT_FILE_ATTR_SET_NEED_FULLPATH(attr, content_ctx->flags &
CONTENT_INFO_FLAG_NEED_FULLPATH);
CONTENT_FILE_ATTR_SET_REQUIRED(attr, (!(flags &
CONTENT_ST_FLAG_CORE_DOES_NOT_NEED_CONTENT)));
#if defined(HAVE_RUNAHEAD)
/* If runahead is supported and we are not using
* subsystems, content data buffer must *always*
* be persistent, since user may toggle second
* instance runahead at any time (and secondary
* core initialisation requires valid data) */
CONTENT_FILE_ATTR_SET_PERSISTENT(attr, true);
#endif
if (string_is_empty(content_path))
{
if ( (flags & CONTENT_ST_FLAG_CORE_DOES_NOT_NEED_CONTENT)
&& content_ctx->flags
& CONTENT_INFO_FLAG_SET_SUPPORTS_NO_GAME_ENABLE)
string_list_append(content, "", attr);
}
else
string_list_append(content, content_path, attr);
}
}
/**
* content_init_file:
*
* Initializes and loads a content file for the currently
* selected libretro core.
*
* Returns : true if successful, otherwise false.
**/
static bool content_file_init(
content_information_ctx_t *content_ctx,
content_state_t *p_content,
struct string_list *content,
enum msg_hash_enums *error_enum,
char **error_string)
{
bool subsystem_path_is_empty = path_is_empty(RARCH_PATH_SUBSYSTEM);
bool ret = subsystem_path_is_empty;
const struct retro_subsystem_info *special = subsystem_path_is_empty ?
NULL : content_file_init_subsystem(content_ctx->subsystem.data,
content_ctx->subsystem.size, error_enum, error_string, &ret);
if (!ret)
return false;
content_file_set_attributes(content, special, content_ctx, error_string);
if (content->size > 0)
{
content_file_list_t *file_list = content_file_list_init(content->size);
if (file_list)
{
p_content->content_list = file_list;
ret = content_file_load(p_content, content, content_ctx,
error_enum, error_string, special);
content_file_list_free_transient_data(p_content->content_list);
return ret;
}
}
if (!special)
{
*error_enum = MSG_ERROR_LIBRETRO_CORE_REQUIRES_CONTENT;
return false;
}
return true;
}
/******************************/
/* Content file functions END */
/******************************/
/**
* content_load_init_wrap:
* @args : Input arguments.
* @argc : Count of arguments.
* @argv : Arguments.
*
* Generates an @argc and @argv pair based on @args
* of type rarch_main_wrap.
**/
static void content_load_init_wrap(
const struct rarch_main_wrap *args,
int *argc, char **argv)
{
*argc = 0;
argv[(*argc)++] = strldup("retroarch", sizeof("retroarch"));
if (args->content_path)
{
RARCH_LOG("[Core]: Using content: \"%s\".\n", args->content_path);
argv[(*argc)++] = strdup(args->content_path);
}
#ifdef HAVE_MENU
else
{
RARCH_LOG("[Core]: %s\n",
msg_hash_to_str(MSG_NO_CONTENT_STARTING_DUMMY_CORE));
argv[(*argc)++] = strldup("--menu", sizeof("--menu"));
}
#endif
if (args->sram_path)
{
argv[(*argc)++] = strldup("-s", sizeof("-s"));
argv[(*argc)++] = strdup(args->sram_path);
}
if (args->state_path)
{
argv[(*argc)++] = strldup("-S", sizeof("-S"));
argv[(*argc)++] = strdup(args->state_path);
}
if (args->config_path)
{
argv[(*argc)++] = strldup("-c", sizeof("-c"));
argv[(*argc)++] = strdup(args->config_path);
}
#ifdef HAVE_DYNAMIC
if (args->libretro_path)
{
argv[(*argc)++] = strldup("-L", sizeof("-L"));
argv[(*argc)++] = strdup(args->libretro_path);
}
#endif
if (args->flags & RARCH_MAIN_WRAP_FLAG_VERBOSE)
argv[(*argc)++] = strldup("-v", sizeof("-v"));
}
/**
* content_load:
*
* Loads content file and starts up RetroArch.
* If no content file can be loaded, will start up RetroArch
* as-is.
*
* Returns: false (0) if retroarch_main_init failed,
* otherwise true (1).
**/
static bool content_load(content_ctx_info_t *info,
content_state_t *p_content)
{
unsigned i = 0;
bool success = false;
int rarch_argc = 0;
char *rarch_argv[MAX_ARGS] = {NULL};
char *argv_copy [MAX_ARGS] = {NULL};
char **rarch_argv_ptr = (char**)info->argv;
int *rarch_argc_ptr = (int*)&info->argc;
struct rarch_main_wrap *wrap_args = NULL;
if (!(wrap_args = (struct rarch_main_wrap*)
malloc(sizeof(*wrap_args))))
return false;
wrap_args->argv = NULL;
wrap_args->content_path = NULL;
wrap_args->sram_path = NULL;
wrap_args->state_path = NULL;
wrap_args->config_path = NULL;
wrap_args->libretro_path = NULL;
wrap_args->flags = 0;
wrap_args->argc = 0;
if (info->environ_get)
info->environ_get(rarch_argc_ptr,
rarch_argv_ptr, info->args, wrap_args);
if (wrap_args->flags & RARCH_MAIN_WRAP_FLAG_TOUCHED)
{
content_load_init_wrap(wrap_args, &rarch_argc, rarch_argv);
memcpy(argv_copy, rarch_argv, sizeof(rarch_argv));
rarch_argv_ptr = (char**)rarch_argv;
rarch_argc_ptr = (int*)&rarch_argc;
}
retroarch_ctl(RARCH_CTL_MAIN_DEINIT, NULL);
wrap_args->argc = *rarch_argc_ptr;
wrap_args->argv = rarch_argv_ptr;
success = retroarch_main_init(wrap_args->argc, wrap_args->argv);
for (i = 0; i < ARRAY_SIZE(argv_copy); i++)
free(argv_copy[i]);
free(wrap_args);
if (!success)
return false;
if (p_content->flags & CONTENT_ST_FLAG_PENDING_SUBSYSTEM_INIT)
{
command_event(CMD_EVENT_CORE_INIT, NULL);
content_clear_subsystem();
}
#ifdef HAVE_GFX_WIDGETS
#ifdef HAVE_CONFIGFILE
/* If retroarch_main_init() returned true, we
* can safely trigger a load content animation */
if (gfx_widgets_ready())
{
/* Note: Have to read settings value here
* (It will be invalid if we try to read
* it earlier...) */
settings_t *settings = config_get_ptr();
bool show_load_content_animation = settings && settings->bools.menu_show_load_content_animation;
if (show_load_content_animation)
gfx_widget_start_load_content_animation();
}
#endif
#endif
#ifdef HAVE_MENU
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
menu_shader_manager_init();
#endif
#endif
command_event(CMD_EVENT_HISTORY_INIT, NULL);
retroarch_favorites_init();
command_event(CMD_EVENT_RESUME, NULL);
command_event(CMD_EVENT_VIDEO_SET_ASPECT_RATIO, NULL);
frontend_driver_process_args(rarch_argc_ptr, rarch_argv_ptr);
frontend_driver_content_loaded();
return true;
}
void menu_content_environment_get(int *argc, char *argv[],
void *args, void *params_data)
{
struct rarch_main_wrap *wrap_args = (struct rarch_main_wrap*)params_data;
runloop_state_t *runloop_st = runloop_state_get_ptr();
rarch_system_info_t *sys_info = &runloop_st->system;
if (!wrap_args)
return;
if (sys_info->load_no_content)
wrap_args->flags |= RARCH_MAIN_WRAP_FLAG_NO_CONTENT;
if (!retroarch_override_setting_is_set(
RARCH_OVERRIDE_SETTING_VERBOSITY, NULL))
{
if (verbosity_is_enabled())
wrap_args->flags |= RARCH_MAIN_WRAP_FLAG_VERBOSE;
}
wrap_args->flags |= RARCH_MAIN_WRAP_FLAG_TOUCHED;
wrap_args->config_path = NULL;
wrap_args->sram_path = NULL;
wrap_args->state_path = NULL;
wrap_args->content_path = NULL;
if (!path_is_empty(RARCH_PATH_CONFIG))
wrap_args->config_path = path_get(RARCH_PATH_CONFIG);
if (!string_is_empty(dir_get_ptr(RARCH_DIR_SAVEFILE)))
wrap_args->sram_path = dir_get_ptr(RARCH_DIR_SAVEFILE);
if (!string_is_empty(dir_get_ptr(RARCH_DIR_SAVESTATE)))
wrap_args->state_path = dir_get_ptr(RARCH_DIR_SAVESTATE);
if (!path_is_empty(RARCH_PATH_CONTENT))
wrap_args->content_path = path_get(RARCH_PATH_CONTENT);
if (!retroarch_override_setting_is_set(
RARCH_OVERRIDE_SETTING_LIBRETRO, NULL))
wrap_args->libretro_path = string_is_empty(path_get(RARCH_PATH_CORE))
? NULL
: path_get(RARCH_PATH_CORE);
}
/**
* task_push_to_history_list:
*
* Will push the content entry to the history playlist.
**/
static void task_push_to_history_list(
content_state_t *p_content,
bool launched_from_menu,
bool launched_from_cli,
bool launched_from_companion_ui)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
uint8_t flags = content_get_flags();
/* Push entry to top of history playlist */
if ( (flags & CONTENT_ST_FLAG_IS_INITED)
|| (flags & CONTENT_ST_FLAG_CORE_DOES_NOT_NEED_CONTENT))
{
char tmp[PATH_MAX_LENGTH];
const char *path_content = path_get(RARCH_PATH_CONTENT);
struct retro_system_info *sysinfo = &runloop_st->system.info;
if (!string_is_empty(path_content))
{
strlcpy(tmp, path_content, sizeof(tmp));
/* Path can be relative here.
* Ensure we're pushing absolute path. */
if (!launched_from_menu)
path_resolve_realpath(tmp, sizeof(tmp), true);
}
else
tmp[0] = '\0';
#ifdef HAVE_MENU
/* Push Quick Menu onto menu stack */
if (launched_from_cli)
menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUICK_MENU, NULL);
#endif
if (sysinfo && !string_is_empty(tmp))
{
const char *core_path = NULL;
const char *core_name = NULL;
const char *label = NULL;
const char *crc32 = NULL;
const char *db_name = NULL;
playlist_t *playlist_hist = g_defaults.content_history;
settings_t *settings = config_get_ptr();
switch (path_is_media_type(tmp))
{
case RARCH_CONTENT_MOVIE:
#ifdef HAVE_FFMPEG
playlist_hist = g_defaults.video_history;
core_name = "movieplayer";
core_path = "builtin";
#endif
break;
case RARCH_CONTENT_MUSIC:
playlist_hist = g_defaults.music_history;
core_name = "musicplayer";
core_path = "builtin";
break;
case RARCH_CONTENT_IMAGE:
#ifdef HAVE_IMAGEVIEWER
playlist_hist = g_defaults.image_history;
core_name = "imageviewer";
core_path = "builtin";
#endif
break;
default:
{
core_info_t *core_info = NULL;
/* Set core display name
* (As far as I can tell, core_info_get_current_core()
* should always provide a valid pointer here...) */
core_info_get_current_core(&core_info);
/* Set core path */
core_path = path_get(RARCH_PATH_CORE);
if (core_info)
core_name = core_info->display_name;
if (string_is_empty(core_name))
core_name = sysinfo->library_name;
if (launched_from_companion_ui)
{
/* Database name + checksum are supplied
* by the companion UI itself */
if (!string_is_empty(p_content->companion_ui_crc32))
crc32 = p_content->companion_ui_crc32;
if (!string_is_empty(p_content->companion_ui_db_name))
db_name = p_content->companion_ui_db_name;
}
#ifdef HAVE_MENU
else
{
menu_handle_t *menu = menu_state_get_ptr()->driver_data;
/* Set database name + checksum */
if (menu)
{
playlist_t *playlist_curr = playlist_get_cached();
if (playlist_index_is_valid(playlist_curr, menu->rpl_entry_selection_ptr, tmp, core_path))
{
playlist_get_crc32(playlist_curr, menu->rpl_entry_selection_ptr, &crc32);
playlist_get_db_name(playlist_curr, menu->rpl_entry_selection_ptr, &db_name);
}
}
}
#endif
break;
}
}
if (!string_is_empty(runloop_st->name.label))
label = runloop_st->name.label;
if (
settings && settings->bools.history_list_enable
&& playlist_hist)
{
char subsystem_name[PATH_MAX_LENGTH];
struct playlist_entry entry = {0};
subsystem_name[0] = '\0';
content_get_subsystem_friendly_name(path_get(RARCH_PATH_SUBSYSTEM), subsystem_name, sizeof(subsystem_name));
/* The push function reads our entry as const,
* so these casts are safe */
entry.path = (char*)tmp;
entry.label = (char*)label;
entry.core_path = (char*)core_path;
entry.core_name = (char*)core_name;
entry.crc32 = (char*)crc32;
entry.db_name = (char*)db_name;
entry.subsystem_ident = (char*)path_get(RARCH_PATH_SUBSYSTEM);
entry.subsystem_name = (char*)subsystem_name;
entry.subsystem_roms = (struct string_list*)path_get_subsystem_list();
entry.entry_slot = runloop_st->entry_state_slot;
command_playlist_push_write(playlist_hist, &entry);
#if TARGET_OS_TV
update_topshelf();
#endif
}
}
}
}
#ifndef HAVE_DYNAMIC
/**
* task_push_to_history_list_from_playlist_pre_load_static:
*
* Will push content from the currently selected playlist
* to the history playlist before loading content. Required
* on static platforms, where essential playlist information
* (label, db_name, etc.) would otherwise be lost when the
* core is forked.
**/
static bool task_push_to_history_list_from_playlist_pre_load_static(
const char *content_path,
const char *core)
{
const char *core_path = NULL;
const char *core_name = NULL;
const char *label = NULL;
const char *crc32 = NULL;
const char *db_name = NULL;
unsigned ss_entry_slot = 0;
playlist_t *playlist_hist = g_defaults.content_history;
settings_t *settings = config_get_ptr();
#ifdef HAVE_MENU
menu_handle_t *menu = menu_state_get_ptr()->driver_data;
#endif
if (!settings ||
!settings->bools.history_list_enable ||
string_is_empty(content_path))
return false;
switch (path_is_media_type(content_path))
{
case RARCH_CONTENT_MOVIE:
#ifdef HAVE_FFMPEG
playlist_hist = g_defaults.video_history;
core_name = "movieplayer";
core_path = "builtin";
#endif
break;
case RARCH_CONTENT_MUSIC:
playlist_hist = g_defaults.music_history;
core_name = "musicplayer";
core_path = "builtin";
break;
case RARCH_CONTENT_IMAGE:
#ifdef HAVE_IMAGEVIEWER
playlist_hist = g_defaults.image_history;
core_name = "imageviewer";
core_path = "builtin";
#endif
break;
default:
{
core_info_t *core_info = NULL;
if (!string_is_empty(core) &&
core_info_find(core, &core_info))
{
/* Set core path and core display name */
core_path = core_info->path;
core_name = core_info->display_name;
#ifdef HAVE_MENU
/* Read remaining information from currently selected
* playlist entry (label, database name, checksum,
* save state slot) */
if (menu)
{
playlist_t *playlist_curr = playlist_get_cached();
if (playlist_index_is_valid(playlist_curr,
menu->rpl_entry_selection_ptr,
content_path, core_path))
{
const struct playlist_entry *pl_entry = NULL;
playlist_get_index(playlist_curr,
menu->rpl_entry_selection_ptr,
&pl_entry);
if (pl_entry)
{
label = pl_entry->label;
crc32 = pl_entry->crc32;
ss_entry_slot = pl_entry->entry_slot;
}
playlist_get_db_name(playlist_curr,
menu->rpl_entry_selection_ptr,
&db_name);
}
}
#endif
}
break;
}
}
if (!string_is_empty(core_path) &&
playlist_hist)
{
struct playlist_entry new_entry = {0};
/* The push function reads our entry as const,
* so these casts are safe */
new_entry.path = (char*)content_path;
new_entry.label = (char*)label;
new_entry.core_path = (char*)core_path;
new_entry.core_name = (char*)core_name;
new_entry.crc32 = (char*)crc32;
new_entry.db_name = (char*)db_name;
new_entry.entry_slot = ss_entry_slot;
/* TODO/FIXME: Subsystems are not properly supported
* on static platforms, so exclude the following:
* - subsystem_ident
* - subsystem_name
* - subsystem_roms */
command_playlist_push_write(playlist_hist, &new_entry);
return true;
}
return false;
}
#endif
#ifdef HAVE_MENU
static bool command_event_cmd_exec(
content_state_t *p_content,
const char *data,
content_information_ctx_t *content_ctx,
bool launched_from_cli
)
{
if (path_get(RARCH_PATH_CONTENT) != data)
{
path_clear(RARCH_PATH_CONTENT);
if (!string_is_empty(data))
path_set(RARCH_PATH_CONTENT, data);
}
#if defined(HAVE_DYNAMIC)
{
content_ctx_info_t content_info;
content_info.argc = 0;
content_info.argv = NULL;
content_info.args = NULL;
content_info.environ_get = menu_content_environment_get;
/* Loads content into currently selected core. */
if (!content_load(&content_info, p_content))
return false;
task_push_to_history_list(p_content, true, launched_from_cli, false);
}
#else
frontend_driver_set_fork(FRONTEND_FORK_CORE_WITH_ARGS);
#endif
return true;
}
#endif
static bool firmware_update_status(
content_information_ctx_t *content_ctx)
{
char s[PATH_MAX_LENGTH];
core_info_ctx_firmware_t firmware_info;
bool set_missing_firmware = false;
core_info_t *core_info = NULL;
runloop_state_t *runloop_st = runloop_state_get_ptr();
core_info_get_current_core(&core_info);
if (!core_info)
return false;
s[0] = '\0';
firmware_info.path = core_info->path;
if (!string_is_empty(content_ctx->directory_system))
firmware_info.directory.system = content_ctx->directory_system;
else
{
strlcpy(s, path_get(RARCH_PATH_CONTENT), sizeof(s));
path_basedir_wrapper(s);
firmware_info.directory.system = s;
}
RARCH_LOG("[Content]: Updating firmware status for: \"%s\" on \"%s\".\n",
core_info->path,
firmware_info.directory.system);
core_info_list_update_missing_firmware(&firmware_info,
&set_missing_firmware);
if (set_missing_firmware)
runloop_st->missing_bios = true;
else
runloop_st->missing_bios = false;
if (
(content_ctx->flags & CONTENT_INFO_FLAG_BIOS_IS_MISSING)
&& (content_ctx->flags & CONTENT_INFO_FLAG_CHECK_FW_BEFORE_LOADING))
{
runloop_msg_queue_push(
msg_hash_to_str(MSG_FIRMWARE),
100, 500, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_LOG("[Content]: Load content blocked. Reason: %s\n",
msg_hash_to_str(MSG_FIRMWARE));
return true;
}
return false;
}
bool task_push_start_dummy_core(content_ctx_info_t *content_info)
{
content_information_ctx_t content_ctx;
content_state_t *p_content = content_state_get_ptr();
bool ret = true;
settings_t *settings = config_get_ptr();
runloop_state_t *runloop_st = runloop_state_get_ptr();
rarch_system_info_t *sys_info = &runloop_st->system;
const char *path_dir_system = settings->paths.directory_system;
bool check_firmware_before_loading = settings->bools.check_firmware_before_loading;
#ifdef HAVE_PATCH
uint16_t rarch_flags = retroarch_get_flags();
#endif
if (!content_info)
return false;
content_ctx.flags = 0;
if (check_firmware_before_loading)
content_ctx.flags |= CONTENT_INFO_FLAG_CHECK_FW_BEFORE_LOADING;
#ifdef HAVE_PATCH
if (rarch_flags & RARCH_FLAGS_IPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_IPS_PREF;
if (rarch_flags & RARCH_FLAGS_BPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_BPS_PREF;
if (rarch_flags & RARCH_FLAGS_UPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_UPS_PREF;
#ifdef HAVE_XDELTA
if (rarch_flags & RARCH_FLAGS_XDELTA_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_XDELTA_PREF;
#endif /* HAVE_XDELTA */
if (runloop_st->flags & RUNLOOP_FLAG_PATCH_BLOCKED)
content_ctx.flags |= CONTENT_INFO_FLAG_PATCH_IS_BLOCKED;
#endif
if (runloop_st->missing_bios)
content_ctx.flags |= CONTENT_INFO_FLAG_BIOS_IS_MISSING;
content_ctx.directory_system = NULL;
content_ctx.directory_cache = NULL;
content_ctx.name_ips = NULL;
content_ctx.name_bps = NULL;
content_ctx.name_ups = NULL;
content_ctx.name_xdelta = NULL;
content_ctx.valid_extensions = NULL;
content_ctx.subsystem.data = NULL;
content_ctx.subsystem.size = 0;
if (!string_is_empty(runloop_st->name.ips))
content_ctx.name_ips = strdup(runloop_st->name.ips);
if (!string_is_empty(runloop_st->name.bps))
content_ctx.name_bps = strdup(runloop_st->name.bps);
if (!string_is_empty(runloop_st->name.ups))
content_ctx.name_ups = strdup(runloop_st->name.ups);
if (!string_is_empty(runloop_st->name.xdelta))
content_ctx.name_xdelta = strdup(runloop_st->name.xdelta);
if (!string_is_empty(path_dir_system))
content_ctx.directory_system = strdup(path_dir_system);
if (!content_info->environ_get)
content_info->environ_get = menu_content_environment_get;
/* Clear content path */
path_clear(RARCH_PATH_CONTENT);
/* Preliminary stuff that has to be done before we
* load the actual content. Can differ per mode. */
sys_info->load_no_content = false;
retroarch_ctl(RARCH_CTL_STATE_FREE, NULL);
task_queue_deinit();
retroarch_init_task_queue();
/* Loads content into currently selected core. */
if ((ret = content_load(content_info, p_content)))
task_push_to_history_list(p_content, false, false, false);
if (content_ctx.name_ips)
free(content_ctx.name_ips);
if (content_ctx.name_bps)
free(content_ctx.name_bps);
if (content_ctx.name_ups)
free(content_ctx.name_ups);
if (content_ctx.name_xdelta)
free(content_ctx.name_xdelta);
if (content_ctx.directory_system)
free(content_ctx.directory_system);
return ret;
}
#ifdef HAVE_MENU
bool task_push_load_content_from_playlist_from_menu(
const char *core_path,
const char *fullpath,
const char *label,
content_ctx_info_t *content_info,
retro_task_callback_t cb,
void *user_data)
{
content_information_ctx_t content_ctx;
content_state_t *p_content = content_state_get_ptr();
bool ret = true;
settings_t *settings = config_get_ptr();
runloop_state_t *runloop_st = runloop_state_get_ptr();
rarch_system_info_t *sys_info = &runloop_st->system;
const char *path_dir_system = settings->paths.directory_system;
#ifndef HAVE_DYNAMIC
bool force_core_reload = settings->bools.always_reload_core_on_run_content;
#endif
bool check_firmware_before_loading = settings->bools.check_firmware_before_loading;
#ifdef HAVE_PATCH
uint16_t rarch_flags = retroarch_get_flags();
#endif
content_ctx.flags = 0;
if (check_firmware_before_loading)
content_ctx.flags |= CONTENT_INFO_FLAG_CHECK_FW_BEFORE_LOADING;
#ifdef HAVE_PATCH
if (rarch_flags & RARCH_FLAGS_IPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_IPS_PREF;
if (rarch_flags & RARCH_FLAGS_BPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_BPS_PREF;
if (rarch_flags & RARCH_FLAGS_UPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_UPS_PREF;
#ifdef HAVE_XDELTA
if (rarch_flags & RARCH_FLAGS_XDELTA_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_XDELTA_PREF;
#endif /* HAVE_XDELTA */
if (runloop_st->flags & RUNLOOP_FLAG_PATCH_BLOCKED)
content_ctx.flags |= CONTENT_INFO_FLAG_PATCH_IS_BLOCKED;
#endif
if (runloop_st->missing_bios)
content_ctx.flags |= CONTENT_INFO_FLAG_BIOS_IS_MISSING;
content_ctx.directory_system = NULL;
content_ctx.directory_cache = NULL;
content_ctx.name_ips = NULL;
content_ctx.name_bps = NULL;
content_ctx.name_ups = NULL;
content_ctx.name_xdelta = NULL;
content_ctx.valid_extensions = NULL;
content_ctx.subsystem.data = NULL;
content_ctx.subsystem.size = 0;
if (!string_is_empty(runloop_st->name.ips))
content_ctx.name_ips = strdup(runloop_st->name.ips);
if (!string_is_empty(runloop_st->name.bps))
content_ctx.name_bps = strdup(runloop_st->name.bps);
if (!string_is_empty(runloop_st->name.ups))
content_ctx.name_ups = strdup(runloop_st->name.ups);
if (!string_is_empty(runloop_st->name.xdelta))
content_ctx.name_xdelta = strdup(runloop_st->name.xdelta);
if (label)
strlcpy(runloop_st->name.label, label, sizeof(runloop_st->name.label));
else
runloop_st->name.label[0] = '\0';
if (!string_is_empty(path_dir_system))
content_ctx.directory_system = strdup(path_dir_system);
/* Is content required by this core? */
if (fullpath)
sys_info->load_no_content = false;
else
sys_info->load_no_content = true;
#ifndef HAVE_DYNAMIC
/* Check whether specified core is already loaded
* > If so, content can be launched directly with
* the currently loaded core */
if (!force_core_reload &&
retroarch_ctl(RARCH_CTL_IS_CORE_LOADED, (void*)core_path))
{
if (!content_info->environ_get)
content_info->environ_get = menu_content_environment_get;
/* Register content path */
path_clear(RARCH_PATH_CONTENT);
if (!string_is_empty(fullpath))
path_set(RARCH_PATH_CONTENT, fullpath);
/* Load content */
if (!(ret = content_load(content_info, p_content)))
goto end;
/* Update content history */
task_push_to_history_list(p_content, true, false, false);
goto end;
}
#endif
/* Specified core is not loaded
* > Load it */
path_set(RARCH_PATH_CORE, core_path);
#ifdef HAVE_DYNAMIC
command_event(CMD_EVENT_LOAD_CORE, NULL);
#else
/* On targets that do not support dynamic core loading,
* must push content to the history list before calling
* command_event_cmd_exec() or playlist metadata will
* be lost */
task_push_to_history_list_from_playlist_pre_load_static(
fullpath, core_path);
#endif
/* Load content
* > On targets that do not support dynamic core loading,
* command_event_cmd_exec() will fork a new instance */
if (!(ret = command_event_cmd_exec(p_content,
fullpath, &content_ctx, false)))
goto end;
#ifdef HAVE_COCOATOUCH
/* This seems to be needed for iOS for some reason
* to show the quick menu after the menu is shown */
menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUICK_MENU, NULL);
#endif
#ifndef HAVE_DYNAMIC
/* No dynamic core loading support: if we reach
* this point then a new instance has been
* forked - have to shut down this one */
retroarch_ctl(RARCH_CTL_SET_SHUTDOWN, NULL);
retroarch_menu_running_finished(true);
#endif
end:
/* Handle load content failure */
if (!ret)
retroarch_menu_running();
if (content_ctx.name_ips)
free(content_ctx.name_ips);
if (content_ctx.name_bps)
free(content_ctx.name_bps);
if (content_ctx.name_ups)
free(content_ctx.name_ups);
if (content_ctx.name_xdelta)
free(content_ctx.name_xdelta);
if (content_ctx.directory_system)
free(content_ctx.directory_system);
return ret;
}
#endif
bool task_push_start_current_core(content_ctx_info_t *content_info)
{
content_information_ctx_t content_ctx;
bool ret = true;
content_state_t *p_content = content_state_get_ptr();
settings_t *settings = config_get_ptr();
runloop_state_t *runloop_st = runloop_state_get_ptr();
const char *path_dir_system = settings->paths.directory_system;
bool check_firmware_before_loading = settings->bools.check_firmware_before_loading;
if (!content_info)
return false;
content_ctx.flags = 0;
if (check_firmware_before_loading)
content_ctx.flags |= CONTENT_INFO_FLAG_CHECK_FW_BEFORE_LOADING;
#ifdef HAVE_PATCH
{
uint16_t rarch_flags = retroarch_get_flags();
if (rarch_flags & RARCH_FLAGS_IPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_IPS_PREF;
if (rarch_flags & RARCH_FLAGS_BPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_BPS_PREF;
if (rarch_flags & RARCH_FLAGS_UPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_UPS_PREF;
#ifdef HAVE_XDELTA
if (rarch_flags & RARCH_FLAGS_XDELTA_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_XDELTA_PREF;
#endif
if (runloop_st->flags & RUNLOOP_FLAG_PATCH_BLOCKED)
content_ctx.flags |= CONTENT_INFO_FLAG_PATCH_IS_BLOCKED;
}
#endif
if (runloop_st->missing_bios)
content_ctx.flags |= CONTENT_INFO_FLAG_BIOS_IS_MISSING;
content_ctx.directory_system = NULL;
content_ctx.directory_cache = NULL;
content_ctx.name_ips = NULL;
content_ctx.name_bps = NULL;
content_ctx.name_ups = NULL;
content_ctx.name_xdelta = NULL;
content_ctx.valid_extensions = NULL;
content_ctx.subsystem.data = NULL;
content_ctx.subsystem.size = 0;
if (!string_is_empty(runloop_st->name.ips))
content_ctx.name_ips = strdup(runloop_st->name.ips);
if (!string_is_empty(runloop_st->name.bps))
content_ctx.name_bps = strdup(runloop_st->name.bps);
if (!string_is_empty(runloop_st->name.ups))
content_ctx.name_ups = strdup(runloop_st->name.ups);
if (!string_is_empty(runloop_st->name.xdelta))
content_ctx.name_xdelta = strdup(runloop_st->name.xdelta);
if (!string_is_empty(path_dir_system))
content_ctx.directory_system = strdup(path_dir_system);
if (!content_info->environ_get)
content_info->environ_get = menu_content_environment_get;
/* Clear content path */
path_clear(RARCH_PATH_CONTENT);
/* Preliminary stuff that has to be done before we
* load the actual content. Can differ per mode. */
runloop_set_current_core_type(CORE_TYPE_PLAIN, true);
/* Load content */
if (firmware_update_status(&content_ctx))
goto end;
/* Loads content into currently selected core.
* Note that 'content_load()' can fail and yet still
* return 'true'... In this case, the dummy core
* will be loaded; the 'start core' operation can
* therefore only be considered successful if the
* dummy core is not running following 'content_load()' */
if (!(ret = content_load(content_info, p_content)) ||
!(ret = (runloop_st->current_core_type != CORE_TYPE_DUMMY)))
{
#ifdef HAVE_MENU
retroarch_menu_running();
#endif
goto end;
}
task_push_to_history_list(p_content, true, false, false);
#ifdef HAVE_MENU
/* Push Quick Menu onto menu stack */
menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUICK_MENU, NULL);
#endif
end:
if (content_ctx.name_ips)
free(content_ctx.name_ips);
if (content_ctx.name_bps)
free(content_ctx.name_bps);
if (content_ctx.name_ups)
free(content_ctx.name_ups);
if (content_ctx.name_xdelta)
free(content_ctx.name_xdelta);
if (content_ctx.directory_system)
free(content_ctx.directory_system);
return ret;
}
bool task_push_load_new_core(
const char *core_path,
const char *fullpath,
content_ctx_info_t *content_info,
enum rarch_core_type type,
retro_task_callback_t cb,
void *user_data)
{
path_set(RARCH_PATH_CORE, core_path);
/* Load core */
command_event(CMD_EVENT_LOAD_CORE, NULL);
#ifndef HAVE_DYNAMIC
/* Fork core? */
if (!frontend_driver_set_fork(FRONTEND_FORK_CORE))
return false;
#endif
/* Preliminary stuff that has to be done before we
* load the actual content. Can differ per mode. */
runloop_set_current_core_type(type, true);
return true;
}
#ifdef HAVE_MENU
bool task_push_load_contentless_core_from_menu(
const char *core_path)
{
#if defined(HAVE_DYNAMIC)
content_ctx_info_t content_info = {0};
#endif
content_information_ctx_t content_ctx = {0};
content_state_t *p_content = content_state_get_ptr();
bool ret = true;
runloop_state_t *runloop_st = runloop_state_get_ptr();
settings_t *settings = config_get_ptr();
const char *path_dir_system = settings->paths.directory_system;
bool check_firmware_before_loading = settings->bools.check_firmware_before_loading;
bool flush_menu = true;
const char *menu_label = NULL;
if (string_is_empty(core_path))
return false;
content_ctx.flags = 0;
if (check_firmware_before_loading)
content_ctx.flags |= CONTENT_INFO_FLAG_CHECK_FW_BEFORE_LOADING;
if (runloop_st->missing_bios)
content_ctx.flags |= CONTENT_INFO_FLAG_BIOS_IS_MISSING;
if (!string_is_empty(path_dir_system))
content_ctx.directory_system = strdup(path_dir_system);
/* Set core path */
path_set(RARCH_PATH_CORE, core_path);
/* Clear content path */
path_clear(RARCH_PATH_CONTENT);
#if defined(HAVE_DYNAMIC)
content_info.environ_get = menu_content_environment_get;
/* Load core */
command_event(CMD_EVENT_LOAD_CORE, NULL);
runloop_set_current_core_type(CORE_TYPE_PLAIN, true);
if (firmware_update_status(&content_ctx))
goto end;
/* Loads content into currently selected core.
* Note that 'content_load()' can fail and yet still
* return 'true'... In this case, the dummy core
* will be loaded; the 'start core' operation can
* therefore only be considered successful if the
* dummy core is not running following 'content_load()' */
if (!(ret = content_load(&content_info, p_content)) ||
!(ret = (runloop_st->current_core_type != CORE_TYPE_DUMMY)))
{
retroarch_menu_running();
goto end;
}
#else
/* TODO/FIXME: Static builds do not support running
* a core directly from the 'command line' without
* supplying a content path, so this *will not work*.
* In order to support this functionality, the '-L'
* command line argument must be enabled for static
* builds to inform the frontend that the core should
* run automatically on launch. We will leave this
* non-functional code here as a place-marker for
* future devs who may wish to implement this... */
command_event_cmd_exec(p_content,
path_get(RARCH_PATH_CONTENT), &content_ctx,
false);
command_event(CMD_EVENT_QUIT, NULL);
#endif
menu_entries_get_last_stack(NULL, &menu_label, NULL, NULL, NULL);
if (string_is_equal(menu_label, msg_hash_to_str(MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB)) ||
string_is_equal(menu_label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_CONTENTLESS_CORES_LIST)))
flush_menu = false;
/* Push Quick Menu onto menu stack */
menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUICK_MENU, &flush_menu);
#ifdef HAVE_DYNAMIC
end:
#endif
if (content_ctx.directory_system)
free(content_ctx.directory_system);
return ret;
}
bool task_push_load_content_with_new_core_from_menu(
const char *core_path,
const char *fullpath,
content_ctx_info_t *content_info,
enum rarch_core_type type,
retro_task_callback_t cb,
void *user_data)
{
content_information_ctx_t content_ctx;
content_state_t *p_content = content_state_get_ptr();
bool ret = true;
settings_t *settings = config_get_ptr();
runloop_state_t *runloop_st = runloop_state_get_ptr();
bool check_firmware_before_loading = settings->bools.check_firmware_before_loading;
const char *path_dir_system = settings->paths.directory_system;
#ifndef HAVE_DYNAMIC
bool force_core_reload = settings->bools.always_reload_core_on_run_content;
/* Check whether specified core is already loaded
* > If so, we can skip loading the core and
* just load the content directly */
if (!force_core_reload &&
(type == CORE_TYPE_PLAIN) &&
retroarch_ctl(RARCH_CTL_IS_CORE_LOADED, (void*)core_path))
return task_push_load_content_with_core(
fullpath, content_info,
type, cb, user_data);
#endif
content_ctx.flags = 0;
if (check_firmware_before_loading)
content_ctx.flags |= CONTENT_INFO_FLAG_CHECK_FW_BEFORE_LOADING;
#ifdef HAVE_PATCH
{
uint16_t rarch_flags = retroarch_get_flags();
if (rarch_flags & RARCH_FLAGS_IPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_IPS_PREF;
if (rarch_flags & RARCH_FLAGS_BPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_BPS_PREF;
if (rarch_flags & RARCH_FLAGS_UPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_UPS_PREF;
#ifdef HAVE_XDELTA
if (rarch_flags & RARCH_FLAGS_XDELTA_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_XDELTA_PREF;
#endif
if (runloop_st->flags & RUNLOOP_FLAG_PATCH_BLOCKED)
content_ctx.flags |= CONTENT_INFO_FLAG_PATCH_IS_BLOCKED;
}
#endif
if (runloop_st->missing_bios)
content_ctx.flags |= CONTENT_INFO_FLAG_BIOS_IS_MISSING;
content_ctx.directory_system = NULL;
content_ctx.directory_cache = NULL;
content_ctx.name_ips = NULL;
content_ctx.name_bps = NULL;
content_ctx.name_ups = NULL;
content_ctx.name_xdelta = NULL;
content_ctx.valid_extensions = NULL;
content_ctx.subsystem.data = NULL;
content_ctx.subsystem.size = 0;
if (!string_is_empty(runloop_st->name.ips))
content_ctx.name_ips = strdup(runloop_st->name.ips);
if (!string_is_empty(runloop_st->name.bps))
content_ctx.name_bps = strdup(runloop_st->name.bps);
if (!string_is_empty(runloop_st->name.ups))
content_ctx.name_ups = strdup(runloop_st->name.ups);
if (!string_is_empty(runloop_st->name.xdelta))
content_ctx.name_xdelta = strdup(runloop_st->name.xdelta);
runloop_st->name.label[0] = '\0';
if (!string_is_empty(path_dir_system))
content_ctx.directory_system = strdup(path_dir_system);
path_set(RARCH_PATH_CONTENT, fullpath);
path_set(RARCH_PATH_CORE, core_path);
#ifdef HAVE_DYNAMIC
/* Load core */
command_event(CMD_EVENT_LOAD_CORE, NULL);
/* Load content */
if (!content_info->environ_get)
content_info->environ_get = menu_content_environment_get;
if (firmware_update_status(&content_ctx))
goto end;
/* Loads content into currently selected core. */
if (!(ret = content_load(content_info, p_content)))
{
retroarch_menu_running();
goto end;
}
task_push_to_history_list(p_content, true, false, false);
#else
command_event_cmd_exec(p_content,
path_get(RARCH_PATH_CONTENT), &content_ctx,
false);
command_event(CMD_EVENT_QUIT, NULL);
#endif
/* Push Quick Menu onto menu stack */
if (type != CORE_TYPE_DUMMY)
menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUICK_MENU, NULL);
#ifdef HAVE_DYNAMIC
end:
#endif
if (content_ctx.name_ips)
free(content_ctx.name_ips);
if (content_ctx.name_bps)
free(content_ctx.name_bps);
if (content_ctx.name_ups)
free(content_ctx.name_ups);
if (content_ctx.name_xdelta)
free(content_ctx.name_xdelta);
if (content_ctx.directory_system)
free(content_ctx.directory_system);
return ret;
}
#endif
static bool task_load_content_internal(
content_ctx_info_t *content_info,
bool loading_from_menu,
bool loading_from_cli,
bool loading_from_companion_ui)
{
content_information_ctx_t content_ctx;
content_state_t *p_content = content_state_get_ptr();
bool ret = false;
runloop_state_t *runloop_st = runloop_state_get_ptr();
rarch_system_info_t *sys_info = &runloop_st->system;
settings_t *settings = config_get_ptr();
bool check_firmware_before_loading = settings->bools.check_firmware_before_loading;
bool set_supports_no_game_enable = settings->bools.set_supports_no_game_enable;
const char *path_dir_system = settings->paths.directory_system;
const char *path_dir_cache = settings->paths.directory_cache;
#ifdef HAVE_PATCH
uint16_t rarch_flags = retroarch_get_flags();
#endif
content_ctx.flags = 0;
if (check_firmware_before_loading)
content_ctx.flags |= CONTENT_INFO_FLAG_CHECK_FW_BEFORE_LOADING;
#ifdef HAVE_PATCH
if (rarch_flags & RARCH_FLAGS_IPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_IPS_PREF;
if (rarch_flags & RARCH_FLAGS_BPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_BPS_PREF;
if (rarch_flags & RARCH_FLAGS_UPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_UPS_PREF;
#ifdef HAVE_XDELTA
if (rarch_flags & RARCH_FLAGS_XDELTA_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_XDELTA_PREF;
#endif /* HAVE_XDELTA */
if (runloop_st->flags & RUNLOOP_FLAG_PATCH_BLOCKED)
content_ctx.flags |= CONTENT_INFO_FLAG_PATCH_IS_BLOCKED;
#endif
if (runloop_st->missing_bios)
content_ctx.flags |= CONTENT_INFO_FLAG_BIOS_IS_MISSING;
content_ctx.directory_system = NULL;
content_ctx.directory_cache = NULL;
content_ctx.name_ips = NULL;
content_ctx.name_bps = NULL;
content_ctx.name_ups = NULL;
content_ctx.name_xdelta = NULL;
content_ctx.valid_extensions = NULL;
content_ctx.subsystem.data = NULL;
content_ctx.subsystem.size = 0;
if (sys_info)
{
struct retro_system_info *sysinfo = &runloop_st->system.info;
if (set_supports_no_game_enable)
content_ctx.flags |= CONTENT_INFO_FLAG_SET_SUPPORTS_NO_GAME_ENABLE;
if (!string_is_empty(path_dir_cache))
content_ctx.directory_cache = strdup(path_dir_cache);
if (!string_is_empty(sysinfo->valid_extensions))
content_ctx.valid_extensions = strdup(sysinfo->valid_extensions);
if (sysinfo->block_extract)
content_ctx.flags |= CONTENT_INFO_FLAG_BLOCK_EXTRACT;
if (sysinfo->need_fullpath)
content_ctx.flags |= CONTENT_INFO_FLAG_NEED_FULLPATH;
content_ctx.subsystem.data = sys_info->subsystem.data;
content_ctx.subsystem.size = sys_info->subsystem.size;
}
if (!string_is_empty(runloop_st->name.ips))
content_ctx.name_ips = strdup(runloop_st->name.ips);
if (!string_is_empty(runloop_st->name.bps))
content_ctx.name_bps = strdup(runloop_st->name.bps);
if (!string_is_empty(runloop_st->name.ups))
content_ctx.name_ups = strdup(runloop_st->name.ups);
if (!string_is_empty(runloop_st->name.xdelta))
content_ctx.name_xdelta = strdup(runloop_st->name.xdelta);
if (!string_is_empty(path_dir_system))
content_ctx.directory_system = strdup(path_dir_system);
if (!content_info->environ_get)
content_info->environ_get = menu_content_environment_get;
if (firmware_update_status(&content_ctx))
goto end;
#ifdef HAVE_PRESENCE
{
presence_userdata_t userdata;
userdata.status = PRESENCE_NETPLAY_NETPLAY_STOPPED;
command_event(CMD_EVENT_PRESENCE_UPDATE, &userdata);
userdata.status = PRESENCE_MENU;
command_event(CMD_EVENT_PRESENCE_UPDATE, &userdata);
}
#endif
/* Loads content into currently selected core. */
if ((ret = content_load(content_info, p_content)))
task_push_to_history_list(p_content,
true, loading_from_cli, loading_from_companion_ui);
end:
if (content_ctx.name_ips)
free(content_ctx.name_ips);
if (content_ctx.name_bps)
free(content_ctx.name_bps);
if (content_ctx.name_ups)
free(content_ctx.name_ups);
if (content_ctx.name_xdelta)
free(content_ctx.name_xdelta);
if (content_ctx.directory_system)
free(content_ctx.directory_system);
if (content_ctx.directory_cache)
free(content_ctx.directory_cache);
if (content_ctx.valid_extensions)
free(content_ctx.valid_extensions);
return ret;
}
static bool task_load_content_internal_wrap(
content_ctx_info_t *content_info,
enum rarch_core_type type,
bool load_from_companion_ui)
{
/* Load content */
if (!task_load_content_internal(content_info, true, false,
load_from_companion_ui))
goto error;
#ifdef HAVE_MENU
/* Push Quick Menu onto menu stack */
if (type != CORE_TYPE_DUMMY)
menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUICK_MENU, NULL);
#endif
return true;
error:
#ifdef HAVE_MENU
retroarch_menu_running();
#endif
return false;
}
bool task_push_load_content_with_new_core_from_companion_ui(
const char *core_path,
const char *fullpath,
const char *label,
const char *db_name,
const char *crc32,
content_ctx_info_t *content_info,
retro_task_callback_t cb,
void *user_data)
{
global_t *global = global_get_ptr();
runloop_state_t *runloop_st = runloop_state_get_ptr();
content_state_t *p_content = content_state_get_ptr();
p_content->companion_ui_db_name[0] = '\0';
p_content->companion_ui_crc32[0] = '\0';
path_set(RARCH_PATH_CONTENT, fullpath);
path_set(RARCH_PATH_CORE, core_path);
if (!string_is_empty(db_name))
strlcpy(p_content->companion_ui_db_name,
db_name, sizeof(p_content->companion_ui_db_name));
if (!string_is_empty(crc32))
strlcpy(p_content->companion_ui_crc32,
crc32, sizeof(p_content->companion_ui_crc32));
#ifdef HAVE_DYNAMIC
command_event(CMD_EVENT_LOAD_CORE, NULL);
#endif
global->launched_from_cli = false;
if (label)
strlcpy(runloop_st->name.label, label, sizeof(runloop_st->name.label));
else
runloop_st->name.label[0] = '\0';
return task_load_content_internal_wrap(content_info, CORE_TYPE_PLAIN, true);
}
bool task_push_load_content_from_cli(
const char *core_path,
const char *fullpath,
content_ctx_info_t *content_info,
enum rarch_core_type type,
retro_task_callback_t cb,
void *user_data)
{
return task_load_content_internal(content_info, true, true, false);
}
bool task_push_start_builtin_core(
content_ctx_info_t *content_info,
enum rarch_core_type type,
retro_task_callback_t cb,
void *user_data)
{
/* Clear content path */
path_clear(RARCH_PATH_CONTENT);
/* Preliminary stuff that has to be done before we
* load the actual content. Can differ per mode. */
runloop_set_current_core_type(type, true);
return task_load_content_internal_wrap(content_info, type, false);
}
bool task_push_load_content_with_core(
const char *fullpath,
content_ctx_info_t *content_info,
enum rarch_core_type type,
retro_task_callback_t cb,
void *user_data)
{
path_set(RARCH_PATH_CONTENT, fullpath);
return task_load_content_internal_wrap(content_info, type, false);
}
bool task_push_load_content_with_current_core_from_companion_ui(
const char *fullpath,
content_ctx_info_t *content_info,
enum rarch_core_type type,
retro_task_callback_t cb,
void *user_data)
{
content_state_t *p_content = content_state_get_ptr();
/* TODO/FIXME: Enable setting of these values
* via function arguments */
p_content->companion_ui_db_name[0] = '\0';
p_content->companion_ui_crc32[0] = '\0';
/* Load content
* > TODO/FIXME: Set loading_from_companion_ui 'false' for
* now, until someone can implement the required higher
* level functionality in 'win32_common.c' and 'ui_cocoa.m' */
path_set(RARCH_PATH_CONTENT, fullpath);
return task_load_content_internal_wrap(content_info, type, false);
}
bool task_push_load_subsystem_with_core(
const char *fullpath,
content_ctx_info_t *content_info,
enum rarch_core_type type,
retro_task_callback_t cb,
void *user_data)
{
content_state_t *p_content = content_state_get_ptr();
p_content->flags |= CONTENT_ST_FLAG_PENDING_SUBSYSTEM_INIT;
return task_load_content_internal_wrap(content_info, type, false);
}
uint8_t content_get_flags(void)
{
content_state_t *p_content = content_state_get_ptr();
return p_content->flags;
}
/* Clears the pending subsystem rom buffer*/
void content_clear_subsystem(void)
{
unsigned i;
content_state_t *p_content = content_state_get_ptr();
p_content->pending_subsystem_rom_id = 0;
p_content->flags &= ~CONTENT_ST_FLAG_PENDING_SUBSYSTEM_INIT;
for (i = 0; i < RARCH_MAX_SUBSYSTEM_ROMS; i++)
{
if (p_content->pending_subsystem_roms[i])
{
free(p_content->pending_subsystem_roms[i]);
p_content->pending_subsystem_roms[i] = NULL;
}
}
}
/* Set the current subsystem*/
void content_set_subsystem(unsigned idx)
{
const struct retro_subsystem_info *subsystem = NULL;
runloop_state_t *runloop_st = runloop_state_get_ptr();
content_state_t *p_content = content_state_get_ptr();
rarch_system_info_t *sys_info = &runloop_st->system;
/* Core fully loaded, use the subsystem data */
if (sys_info->subsystem.data)
subsystem = sys_info->subsystem.data + idx;
/* Core not loaded completely, use the data we peeked on load core */
else
subsystem = runloop_st->subsystem_data + idx;
p_content->pending_subsystem_id = idx;
if ( subsystem
&& (runloop_st->subsystem_current_count > 0))
{
strlcpy(p_content->pending_subsystem_ident,
subsystem->ident, sizeof(p_content->pending_subsystem_ident));
p_content->pending_subsystem_rom_num = subsystem->num_roms;
}
RARCH_LOG("[Subsystem]: Setting current subsystem to: %d(%s) Content amount: %d\n",
p_content->pending_subsystem_id,
p_content->pending_subsystem_ident,
p_content->pending_subsystem_rom_num);
}
/* Sets the subsystem by name */
bool content_set_subsystem_by_name(const char* subsystem_name)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
rarch_system_info_t *sys_info = &runloop_st->system;
unsigned i = 0;
/* Core not loaded completely, use the data we peeked on load core */
const struct retro_subsystem_info
*subsystem = runloop_st->subsystem_data;
/* Core fully loaded, use the subsystem data */
if (sys_info->subsystem.data)
subsystem = sys_info->subsystem.data;
for (i = 0; i < runloop_st->subsystem_current_count; i++, subsystem++)
{
if (string_is_equal(subsystem_name, subsystem->ident))
{
content_set_subsystem(i);
return true;
}
}
return false;
}
void content_get_subsystem_friendly_name(const char* subsystem_name, char* subsystem_friendly_name, size_t len)
{
unsigned i = 0;
runloop_state_t *runloop_st = runloop_state_get_ptr();
rarch_system_info_t *sys_info = &runloop_st->system;
/* Core not loaded completely, use the data we peeked on load core */
const struct retro_subsystem_info *subsystem = runloop_st->subsystem_data;
/* Core fully loaded, use the subsystem data */
if (sys_info->subsystem.data)
subsystem = sys_info->subsystem.data;
for (i = 0; i < runloop_st->subsystem_current_count; i++, subsystem++)
{
if (string_is_equal(subsystem_name, subsystem->ident))
{
strlcpy(subsystem_friendly_name, subsystem->desc, len);
break;
}
}
return;
}
/* Add a rom to the subsystem ROM buffer */
void content_add_subsystem(const char* path)
{
content_state_t *p_content = content_state_get_ptr();
size_t pending_size = PATH_MAX_LENGTH * sizeof(char);
p_content->pending_subsystem_roms[p_content->pending_subsystem_rom_id] = (char*)malloc(pending_size);
strlcpy(p_content->pending_subsystem_roms[
p_content->pending_subsystem_rom_id],
path, pending_size);
RARCH_LOG("[Subsystem]: Subsystem id: %d Subsystem ident:"
" %s Content ID: %d, Content Path: \"%s\".\n",
p_content->pending_subsystem_id,
p_content->pending_subsystem_ident,
p_content->pending_subsystem_rom_id,
p_content->pending_subsystem_roms[
p_content->pending_subsystem_rom_id]);
p_content->pending_subsystem_rom_id++;
}
void content_set_does_not_need_content(void)
{
content_state_t *p_content = content_state_get_ptr();
p_content->flags |= CONTENT_ST_FLAG_CORE_DOES_NOT_NEED_CONTENT;
}
void content_unset_does_not_need_content(void)
{
content_state_t *p_content = content_state_get_ptr();
p_content->flags &= ~CONTENT_ST_FLAG_CORE_DOES_NOT_NEED_CONTENT;
}
#ifndef CRC32_BUFFER_SIZE
#define CRC32_BUFFER_SIZE 1048576
#endif
#ifndef CRC32_MAX_MB
#define CRC32_MAX_MB 64
#endif
/**
* Calculate a CRC32 from the first part of the given file.
* "first part" being the first (CRC32_BUFFER_SIZE * CRC32_MAX_MB)
* bytes.
*
* @return The calculated CRC32 hash, or 0 if there was an error.
*/
static uint32_t file_crc32(uint32_t crc, const char *path)
{
unsigned i;
RFILE *file = NULL;
unsigned char *buf = NULL;
if (!path)
return 0;
if (!(file = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ, 0)))
return 0;
if (!(buf = (unsigned char*)malloc(CRC32_BUFFER_SIZE)))
{
filestream_close(file);
return 0;
}
for (i = 0; i < CRC32_MAX_MB; i++)
{
int64_t nread = filestream_read(file, buf, CRC32_BUFFER_SIZE);
if (nread < 0)
{
free(buf);
filestream_close(file);
return 0;
}
crc = encoding_crc32(crc, buf, (size_t)nread);
if (filestream_eof(file))
break;
}
free(buf);
filestream_close(file);
return crc;
}
uint32_t content_get_crc(void)
{
content_state_t *p_content = content_state_get_ptr();
if (p_content->flags & CONTENT_ST_FLAG_PENDING_ROM_CRC)
{
p_content->flags &= ~CONTENT_ST_FLAG_PENDING_ROM_CRC;
/* TODO/FIXME - file_crc32 has a 64MB max limit -
* get rid of this function and find a better
* way to calculate CRC based on the file */
p_content->rom_crc = file_crc32(0,
(const char*)p_content->pending_rom_crc_path);
RARCH_LOG("[Content]: CRC32: 0x%x.\n",
(unsigned)p_content->rom_crc);
}
return p_content->rom_crc;
}
char* content_get_subsystem_rom(unsigned index)
{
content_state_t *p_content = content_state_get_ptr();
return p_content->pending_subsystem_roms[index];
}
bool content_is_inited(void)
{
content_state_t *p_content = content_state_get_ptr();
return ((p_content->flags & CONTENT_ST_FLAG_IS_INITED) > 0);
}
void content_deinit(void)
{
content_state_t *p_content = content_state_get_ptr();
content_file_override_free(p_content);
content_file_list_free(p_content->content_list);
p_content->content_list = NULL;
p_content->rom_crc = 0;
p_content->flags &= ~(CONTENT_ST_FLAG_PENDING_ROM_CRC
| CONTENT_ST_FLAG_CORE_DOES_NOT_NEED_CONTENT
| CONTENT_ST_FLAG_IS_INITED);
}
/* Set environment variables before a subsystem load */
void content_set_subsystem_info(void)
{
content_state_t *p_content = content_state_get_ptr();
if (!(p_content->flags & CONTENT_ST_FLAG_PENDING_SUBSYSTEM_INIT))
return;
path_set(RARCH_PATH_SUBSYSTEM, p_content->pending_subsystem_ident);
runloop_path_set_special(p_content->pending_subsystem_roms,
p_content->pending_subsystem_rom_num);
}
/* Initializes and loads a content file for the currently
* selected libretro core. */
bool content_init(void)
{
struct string_list content;
content_information_ctx_t content_ctx;
enum msg_hash_enums error_enum = MSG_UNKNOWN;
content_state_t *p_content = content_state_get_ptr();
bool ret = true;
char *error_string = NULL;
runloop_state_t *runloop_st = runloop_state_get_ptr();
rarch_system_info_t *sys_info = &runloop_st->system;
settings_t *settings = config_get_ptr();
bool check_firmware_before_loading = settings->bools.check_firmware_before_loading;
bool set_supports_no_game_enable = settings->bools.set_supports_no_game_enable;
const char *path_dir_system = settings->paths.directory_system;
const char *path_dir_cache = settings->paths.directory_cache;
#ifdef HAVE_PATCH
uint16_t rarch_flags = retroarch_get_flags();
#endif
content_file_list_free(p_content->content_list);
p_content->content_list = NULL;
content_ctx.flags = 0;
if (check_firmware_before_loading)
content_ctx.flags |= CONTENT_INFO_FLAG_CHECK_FW_BEFORE_LOADING;
#ifdef HAVE_PATCH
if (rarch_flags & RARCH_FLAGS_IPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_IPS_PREF;
if (rarch_flags & RARCH_FLAGS_BPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_BPS_PREF;
if (rarch_flags & RARCH_FLAGS_UPS_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_UPS_PREF;
#ifdef HAVE_XDELTA
if (rarch_flags & RARCH_FLAGS_XDELTA_PREF)
content_ctx.flags |= CONTENT_INFO_FLAG_IS_XDELTA_PREF;
#endif /* HAVE_XDELTA */
if (runloop_st->flags & RUNLOOP_FLAG_PATCH_BLOCKED)
content_ctx.flags |= CONTENT_INFO_FLAG_PATCH_IS_BLOCKED;
#endif /* HAVE_PATCH */
content_ctx.directory_system = NULL;
content_ctx.directory_cache = NULL;
content_ctx.name_ips = NULL;
content_ctx.name_bps = NULL;
content_ctx.name_ups = NULL;
content_ctx.name_xdelta = NULL;
content_ctx.valid_extensions = NULL;
content_ctx.subsystem.data = NULL;
content_ctx.subsystem.size = 0;
if (!string_is_empty(runloop_st->name.ips))
content_ctx.name_ips = strdup(runloop_st->name.ips);
if (!string_is_empty(runloop_st->name.bps))
content_ctx.name_bps = strdup(runloop_st->name.bps);
if (!string_is_empty(runloop_st->name.ups))
content_ctx.name_ups = strdup(runloop_st->name.ups);
if (!string_is_empty(runloop_st->name.xdelta))
content_ctx.name_xdelta = strdup(runloop_st->name.xdelta);
if (sys_info)
{
struct retro_system_info *sysinfo = &runloop_st->system.info;
if (set_supports_no_game_enable)
content_ctx.flags |= CONTENT_INFO_FLAG_SET_SUPPORTS_NO_GAME_ENABLE;
if (!string_is_empty(path_dir_system))
content_ctx.directory_system = strdup(path_dir_system);
if (!string_is_empty(path_dir_cache))
content_ctx.directory_cache = strdup(path_dir_cache);
if (!string_is_empty(sysinfo->valid_extensions))
content_ctx.valid_extensions = strdup(sysinfo->valid_extensions);
if (sysinfo->block_extract)
content_ctx.flags |= CONTENT_INFO_FLAG_BLOCK_EXTRACT;
if (sysinfo->need_fullpath)
content_ctx.flags |= CONTENT_INFO_FLAG_NEED_FULLPATH;
content_ctx.subsystem.data = sys_info->subsystem.data;
content_ctx.subsystem.size = sys_info->subsystem.size;
}
p_content->flags |= CONTENT_ST_FLAG_IS_INITED;
if (string_list_initialize(&content))
{
if (!content_file_init(&content_ctx, p_content,
&content, &error_enum, &error_string))
{
content_deinit();
ret = false;
}
string_list_deinitialize(&content);
}
if (content_ctx.name_ips)
free(content_ctx.name_ips);
if (content_ctx.name_bps)
free(content_ctx.name_bps);
if (content_ctx.name_ups)
free(content_ctx.name_ups);
if (content_ctx.name_xdelta)
free(content_ctx.name_xdelta);
if (content_ctx.directory_system)
free(content_ctx.directory_system);
if (content_ctx.directory_cache)
free(content_ctx.directory_cache);
if (content_ctx.valid_extensions)
free(content_ctx.valid_extensions);
if (error_enum != MSG_UNKNOWN)
{
switch (error_enum)
{
case MSG_ERROR_LIBRETRO_CORE_REQUIRES_SPECIAL_CONTENT:
case MSG_ERROR_LIBRETRO_CORE_REQUIRES_VFS:
case MSG_FAILED_TO_LOAD_CONTENT:
case MSG_ERROR_LIBRETRO_CORE_REQUIRES_CONTENT:
RARCH_ERR("[Content]: %s\n", msg_hash_to_str(error_enum));
runloop_msg_queue_push(msg_hash_to_str(error_enum), 2, ret ? 1 : 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
break;
case MSG_UNKNOWN:
default:
break;
}
}
if (error_string)
{
if (ret)
RARCH_LOG("[Content]: %s\n", error_string);
else
RARCH_ERR("[Content]: %s\n", error_string);
/* Do not flush the message queue here
* > This allows any core-generated error messages
* to propagate through to the frontend */
runloop_msg_queue_push(error_string, 2, ret ? 1 : 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
free(error_string);
}
return ret;
}