Files
RetroArch/tasks/task_content.c
T
LibretroAdmin b9be38fd55 strl: copy string literals without scanning them at runtime
strlcpy() begins with strlen() of its source. It lives in another
translation unit, so with no LTO that scan happens at runtime even when
the source is a literal whose length the compiler already measured, and
the call itself cannot be folded away either.

strlcpy_lit() passes the length over as sizeof(). When the destination
size is also a constant -- sizeof(buf), the usual case -- nothing is
left but the stores memcpy() compiles to:

  strlcpy      "core_options"  sizeof(buf)   7.01 ns
  strlcpy_lit  "core_options"  sizeof(buf)   0.42 ns
  strlcpy      41-char literal sizeof(buf)   7.74 ns
  strlcpy_lit  41-char literal sizeof(buf)   0.71 ns
  strlcpy      "core_options"  runtime len   7.11 ns
  strlcpy_lit  "core_options"  runtime len   0.70 ns

The destination bound is kept. When the literal does not fit it defers
to strlcpy(), so the copy truncates as before rather than overrunning;
a form that took only the literal would have turned every one of these
sites into an overflow waiting for a small buffer. The literal is
concatenated with "" so a non-literal source is a compile error rather
than a sizeof() that silently measures a pointer.

818 sites across 104 files were converted by parsing arguments rather
than by pattern-matching text: only a three-argument call whose second
argument is a bare string literal is rewritten, so a macro that happens
to expand to a literal is left alone. Line endings are preserved, so
the CRLF files carry only their own changed lines.

test/compat/test_strl.c compares the two against each other over every
destination size from 0 past the literal's length, for literals that
are empty, single-character, path-shaped, adjacently concatenated and
escape-bearing, checking the return, the terminator and the whole
buffer so an overrun past the terminator is caught. ASan+UBSan+LSan and
TSan both 4/4. The full menu_displaylist_build_list walk over every
displaylist type is byte-identical to before the change
(md5 a2a081bcac09cdc8d72d67efd14ac40e).
2026-08-30 02:10:20 +02:00

3455 lines
110 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 __APPLE__
#include <TargetConditionals.h>
#endif
#ifdef _WIN32
#ifdef _XBOX
#include <xtl.h>
#define setmode _setmode
#define INVALID_FILE_ATTRIBUTES -1
#else
#include <io.h>
#include <fcntl.h>
#define WIN32_LEAN_AND_MEAN
#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 <formats/data_transfer.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 "../msg_hash_lbl_str.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
#if defined(ANDROID) && defined(HAVE_SAF)
#include <vfs/vfs_implementation_saf.h>
#endif
#include "task_content.h"
#include "patch_stream.h"
#include "tasks_internal.h"
#include "task_content_prefetch.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_PATCH_IS_BLOCKED = (1 << 3),
CONTENT_INFO_FLAG_IS_IPS_PREF = (1 << 4),
CONTENT_INFO_FLAG_IS_BPS_PREF = (1 << 5),
CONTENT_INFO_FLAG_IS_UPS_PREF = (1 << 6),
CONTENT_INFO_FLAG_IS_XDELTA_PREF = (1 << 7)
};
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;
};
#if defined(HAVE_GFX_WIDGETS)
/* True while the deferred load path has a launch notification on
* screen that it started BEFORE the read, so it could carry the read
* percentage. Only one deferral runs at a time
* (CONTENT_ST_FLAG_DEFERRED_LOAD_PENDING), so one flag covers it.
* Consumed by content_load(), which must not restart the card the
* user has been watching. */
static bool content_load_animation_showing = false;
#endif
/*************************************/
/* 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 && (ext && *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)
{
size_t i;
content_state_t *p_content = content_state_get_ptr();
if (!p_content || !overrides)
return false;
/* Free any existing override list */
content_file_override_free(p_content);
for (i = 0; overrides[i].extensions; i++)
{
const char *ptr = overrides[i].extensions;
while (*ptr)
{
char ext[32];
size_t num_entries, _len;
content_file_override_t *override = NULL;
/* Find next '|' delimiter or end of string */
const char *delim = ptr;
while (*delim && *delim != '|')
delim++;
_len = (size_t)(delim - ptr);
/* Extract extension token */
if (_len > 0 && _len < sizeof(ext))
{
memcpy(ext, ptr, _len);
ext[_len] = '\0';
/* Check whether extension has already been
* registered */
if (!content_file_override_get_ext(p_content, ext, NULL))
{
/* 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))
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;
}
}
/* Advance past token and delimiter */
ptr += _len;
if (*ptr == '|')
ptr++;
}
}
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 (!path || !*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 len)
{
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(len, sizeof(content_file_info_t))))
{
file_list->size = len;
/* Create retro_game_info object */
if ((file_list->game_info = (struct retro_game_info *)
calloc(len, sizeof(struct retro_game_info))))
{
/* Create retro_game_info_ext object */
if ((file_list->game_info_ext =
(struct retro_game_info_ext *)
calloc(len, 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)
{
if (file_list && (path && *path))
{
union string_list_elem_attr attr;
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;
file_info = &file_list->entries[idx];
game_info = &file_list->game_info[idx];
game_info_ext = &file_list->game_info_ext[idx];
/* 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 (path && *path)
{
char dir [DIR_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;
file_info->file_in_archive = true;
/* 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 (*archive_path)
file_info->archive_path = strdup(archive_path);
/* Extract name of file in archive */
archive_delim++;
if (archive_delim && *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));
}
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 (*dir)
{
/* Remove any trailing slash */
char *last_slash = find_last_slash(dir);
if (last_slash && (last_slash[1] == '\0'))
*last_slash = '\0';
if (*dir)
file_info->dir = strdup(dir);
}
if (*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 information context helper functions START */
/*****************************************************/
/**
* content_information_ctx_init:
*
* Initialises a content_information_ctx_t, reading
* current settings and runloop state. Caller must
* call content_information_ctx_free() when done.
*
* @param content_ctx : context to initialise.
* @param settings : current settings (may be NULL).
* @param runloop_st : current runloop state (may be NULL).
* @param include_sys_info : if true, also populate fields from
* the system info (valid_extensions,
* block_extract, need_fullpath, subsystem,
* directory_cache, set_supports_no_game).
**/
static void content_information_ctx_init(
content_information_ctx_t *content_ctx,
settings_t *settings,
runloop_state_t *runloop_st,
bool include_sys_info)
{
content_ctx->flags = 0;
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;
#ifdef HAVE_PATCH
{
uint32_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 /* HAVE_XDELTA */
if (runloop_st && (runloop_st->flags & RUNLOOP_FLAG_PATCH_BLOCKED))
content_ctx->flags |= CONTENT_INFO_FLAG_PATCH_IS_BLOCKED;
}
#endif /* HAVE_PATCH */
if (runloop_st)
{
if (*runloop_st->name.ips)
content_ctx->name_ips = strdup(runloop_st->name.ips);
if (*runloop_st->name.bps)
content_ctx->name_bps = strdup(runloop_st->name.bps);
if (*runloop_st->name.ups)
content_ctx->name_ups = strdup(runloop_st->name.ups);
if (*runloop_st->name.xdelta)
content_ctx->name_xdelta = strdup(runloop_st->name.xdelta);
}
if (settings)
{
const char *path_dir_system = settings->paths.directory_system;
if (path_dir_system && *path_dir_system)
content_ctx->directory_system = strdup(path_dir_system);
}
if (include_sys_info && runloop_st)
{
rarch_system_info_t *sys_info = &runloop_st->system;
struct retro_system_info *sysinfo = &sys_info->info;
if (settings && settings->bools.set_supports_no_game_enable)
content_ctx->flags |= CONTENT_INFO_FLAG_SET_SUPPORTS_NO_GAME_ENABLE;
if (settings)
{
const char *path_dir_cache = settings->paths.directory_cache;
if (path_dir_cache && *path_dir_cache)
{
content_ctx->directory_cache = strdup(path_dir_cache);
if (!path_is_directory(path_dir_cache))
path_mkdir(path_dir_cache);
}
}
if (sysinfo->valid_extensions && *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;
}
}
/**
* content_information_ctx_free:
*
* Frees all heap-allocated members of a content_information_ctx_t.
**/
static void content_information_ctx_free(
content_information_ctx_t *content_ctx)
{
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);
}
/***************************************************/
/* Content information context helper functions END */
/***************************************************/
/********************************/
/* Content file functions START */
/********************************/
#define BLCK_BLOCK_EXTRACT 1
#define BLCK_NEED_FULLPATH 2
#define BLCK_REQUIRED 4
#define BLCK_PERSISTENT 8
#ifdef HAVE_COMPRESSION
/* data_transfer source bridge for archive entries */
static int64_t content_file_entry_source_read(void *ud, uint8_t *dst,
size_t n)
{
return file_archive_entry_source_read(
(file_archive_entry_source_t*)ud, dst, (int64_t)n);
}
#endif
/* data_transfer source bridge for a plain file */
static int64_t content_file_plain_source_read(void *ud, uint8_t *dst,
size_t n)
{
return filestream_read((RFILE*)ud, dst, (int64_t)n);
}
#ifdef HAVE_PATCH
/* Open a streaming applier for whatever patch this load would apply, or
* NULL to leave the load on the existing whole-buffer patch pass. */
static patch_stream_t *content_file_patch_stream_open(
content_information_ctx_t *content_ctx,
size_t idx,
enum rarch_content_type first_content_type,
size_t src_len,
void **patch_data,
const char **patch_path)
{
if ( idx != 0
|| first_content_type != RARCH_CONTENT_NONE
|| (content_ctx->flags & CONTENT_INFO_FLAG_PATCH_IS_BLOCKED))
{
*patch_data = NULL;
*patch_path = NULL;
return NULL;
}
return patch_content_stream_open(
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,
src_len, patch_data, patch_path);
}
#endif
#ifdef HAVE_PATCH
/* Abandon a streaming patch whose source never fully arrived.
*
* A stream that has seen only part of its source must never be
* finished: the appliers treat a short source as a legitimately short
* one and zero-fill the rest, so finishing here would silently replace
* the content with a patch applied to a truncated ROM. Dropping the
* stream instead leaves the load to its fallback read, after which the
* ordinary whole-buffer pass patches the complete buffer. */
static void content_file_patch_stream_drop(void **ps, void **patch_data)
{
if (*ps)
{
patch_stream_free((patch_stream_t*)*ps);
*ps = NULL;
}
free(*patch_data);
*patch_data = NULL;
}
#endif
/* Drive a source-mode transfer to completion, advancing a streaming
* patch (if any) over each span as it arrives, and detach the filled
* buffer. Shared by the archive-entry and plain-file loads so both get
* the same interleaving and the same ownership handoff.
*
* Returns the detached buffer (caller frees) or NULL, in which case the
* transfer has already been released. */
static uint8_t *content_file_pump_source(data_transfer_t *dt,
void *patch_stream, size_t *out_len)
{
uint8_t *out = NULL;
size_t avail = 0;
#ifdef HAVE_PATCH
patch_stream_t *ps = (patch_stream_t*)patch_stream;
const uint8_t *base = NULL;
size_t fed = 0;
size_t total = 0;
if (ps)
base = data_transfer_ptr(dt, &total);
#else
(void)patch_stream;
#endif
while ( !data_transfer_complete(dt)
&& !data_transfer_failed(dt))
{
avail = data_transfer_iterate(dt, 0);
#ifdef HAVE_PATCH
if (ps && avail > fed)
{
patch_stream_feed(ps, base + fed, avail - fed);
fed = avail;
/* Once the stream is dead its finish will fail anyway, so
* stop pushing the rest of the content through it - the load
* itself still has to complete. */
if (patch_stream_failed(ps))
ps = NULL;
}
#endif
}
#ifdef HAVE_PATCH
if (ps)
{
avail = data_transfer_avail(dt);
if (avail > fed)
patch_stream_feed(ps, base + fed, avail - fed);
}
#endif
if (!(out = (uint8_t*)data_transfer_source_detach(dt, out_len)))
data_transfer_free(dt);
return out;
}
/* ---- prefetch cache: bytes read ahead of the load ---- */
/* Take (transfer ownership of) a prefetched buffer for this exact
* path, if the prefetch task deposited one. */
static uint8_t *content_file_prefetch_take(content_state_t *p_content,
const char *path, size_t *size)
{
size_t i;
for (i = 0; i < p_content->prefetch_count; i++)
{
if ( p_content->prefetch[i].path
&& string_is_equal(p_content->prefetch[i].path, path))
{
uint8_t *data = p_content->prefetch[i].data;
*size = p_content->prefetch[i].size;
free(p_content->prefetch[i].path);
p_content->prefetch[i].path = NULL;
p_content->prefetch[i].data = NULL;
p_content->prefetch[i].size = 0;
return data;
}
}
return NULL;
}
/**
* 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: non-0 if successful, 0 on error.
**/
static size_t 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)
{
uint8_t *content_data = NULL;
int64_t content_size = 0;
#ifdef HAVE_PATCH
/* Soft patching normally runs as a separate pass once the whole file
* is resident. When the patch can be resolved ahead of the load - it
* depends only on the preference flags and which patch files exist,
* never on the content - the applier is opened here and advanced over
* each span as it arrives, so the patch keeps pace with the load
* instead of following it. NULL means there is nothing streamable
* and the existing patch_content pass below runs unchanged. */
void *patch_data = NULL;
const char *patch_src = NULL;
bool streamed = false;
#endif
/* Streaming patch handle, kept opaque and declared unconditionally so
* the load paths can hand it to the pump without a HAVE_PATCH branch
* of their own. Always NULL when patching is compiled out. */
void *patch_ps = NULL;
RARCH_LOG("[Content] %s: \"%s\".\n",
msg_hash_to_str(MSG_LOADING_CONTENT_FILE), content_path);
/* A prefetched buffer for this exact path short-circuits the
* read entirely: the bytes streamed in ahead of the load, a
* budgeted slice per task tick, while the frontend kept
* running. */
{
size_t pre_size = 0;
if ((content_data = content_file_prefetch_take(p_content,
content_path, &pre_size)))
content_size = (int64_t)pre_size;
}
/* Read content from file into memory buffer */
#ifdef HAVE_COMPRESSION
if (content_data)
{
/* prefetched above */
}
else if (content_compressed)
{
/* Prefer the incremental entry source on the data_transfer
* spine: the entry inflates chunk by chunk directly into the
* exact-size destination as it is read - decompression and
* loading interleave instead of running as one opaque gulp -
* and the pump takes a byte budget, so this read is ready to
* be sliced across task ticks once the surrounding load flow
* is. (Today it is still pumped to completion in place:
* behaviour and bytes are identical to the classic path.)
* Backends without independently decodable entries (7z solid
* blocks) and anything unexpected fall back to the classic
* whole-entry read below. */
int64_t src_usize = 0;
file_archive_entry_source_t *src =
file_archive_entry_source_open(content_path, &src_usize);
if (src)
{
if (src_usize > 0)
{
data_transfer_t *dt = data_transfer_open_source(
(size_t)src_usize, content_file_entry_source_read,
src);
if (dt)
{
size_t out_len = 0;
#ifdef HAVE_PATCH
patch_ps = content_file_patch_stream_open(content_ctx,
idx, first_content_type, (size_t)src_usize,
&patch_data, &patch_src);
#endif
if ((content_data = content_file_pump_source(dt,
patch_ps, &out_len)))
content_size = (int64_t)out_len;
#ifdef HAVE_PATCH
else
content_file_patch_stream_drop(&patch_ps, &patch_data);
#endif
}
}
file_archive_entry_source_close(src);
}
if (!content_data)
{
if (!file_archive_compressed_read(content_path,
(void**)&content_data, NULL, &content_size))
return 0;
}
}
else
#endif
if (!content_data)
{
/* Plain file: pump it through the same source-mode transfer the
* archive path uses, rather than one blocking whole-file read.
* That puts uncompressed content - the common case - on the
* sliceable spine too, and lets a patch advance alongside the
* read instead of running as a pass afterwards.
*
* Falls back to filestream_read_file if the file cannot be
* opened, sized, or transferred, so nothing that loaded before
* stops loading now. */
RFILE *fp = filestream_open(content_path,
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (fp)
{
int64_t fsize = filestream_get_size(fp);
if (fsize > 0)
{
data_transfer_t *dt = data_transfer_open_source((size_t)fsize,
content_file_plain_source_read, fp);
if (dt)
{
size_t out_len = 0;
#ifdef HAVE_PATCH
patch_ps = content_file_patch_stream_open(content_ctx,
idx, first_content_type, (size_t)fsize,
&patch_data, &patch_src);
#endif
if ((content_data = content_file_pump_source(dt,
patch_ps, &out_len)))
content_size = (int64_t)out_len;
#ifdef HAVE_PATCH
else
content_file_patch_stream_drop(&patch_ps, &patch_data);
#endif
}
}
filestream_close(fp);
}
if (!content_data)
{
if (!filestream_read_file(content_path,
(void**)&content_data, &content_size))
return 0;
}
}
if (content_size < 0)
{
/* Nothing downstream can use this, and the patch stream must not
* outlive the function that owns it. */
#ifdef HAVE_PATCH
content_file_patch_stream_drop(&patch_ps, &patch_data);
#endif
free(content_data);
return 0;
}
#ifdef HAVE_PATCH
/* Complete a streamed patch. The source is still fully resident here
* (the transfer buffer we just detached), so a patch that turns out to
* be malformed costs nothing: keep the unpatched buffer and let the
* normal pass below try again, which is exactly what happens today
* when an applier rejects a patch. */
if (patch_ps)
{
uint8_t *patched = NULL;
size_t patched_len = 0;
if ( content_data
&& patch_stream_finish((patch_stream_t*)patch_ps,
&patched, &patched_len)
&& patched)
{
free(content_data);
content_data = patched;
content_size = (int64_t)patched_len;
streamed = true;
if (config_get_ptr()->bools.notification_show_patch_applied)
{
/* Same wording as the whole-buffer path: the patch's
* file name, not the format it happens to be. */
char msg[128];
const char *patch_filename = patch_src
? path_basename_nocompression(patch_src) : NULL;
size_t _len = snprintf(msg, sizeof(msg),
msg_hash_to_str(MSG_APPLYING_PATCH),
patch_filename ? patch_filename :
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_UNKNOWN));
runloop_msg_queue_push(msg, _len, 1, 180, false, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
}
patch_stream_free((patch_stream_t*)patch_ps);
patch_ps = NULL;
free(patch_data);
patch_data = NULL;
}
#endif
/* First content file is significant: attempt to do
* soft patching, CRC checking, etc. */
if (idx == 0)
{
/* If we have a media type, ignore patches. */
if (first_content_type == RARCH_CONTENT_NONE)
{
#ifdef HAVE_PATCH
/* Attempt to apply a patch, unless one was already streamed
* alongside the load above. */
if ( !streamed
&& !(content_ctx->flags & CONTENT_INFO_FLAG_PATCH_IS_BLOCKED))
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
}
}
*data = content_data;
return (size_t)content_size;
}
#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 **err_string)
{
const char *tmp_path_ptr = NULL;
size_t _len;
unsigned i;
char tmp_path[PATH_MAX_LENGTH];
char tmp_dir[DIR_MAX_LENGTH];
tmp_path[0] = '\0';
/* TODO/FIXME - localize */
RARCH_LOG("[Content] Core requires uncompressed content - "
"extracting archive to temporary directory...\n");
/* The member is written under a directory of our own rather than
* straight into the cache or content directory. Extraction keeps
* the member's own basename - savefile and savestate paths are
* derived from it, so a uniquified file name would silently move
* a user's saves - and a name that is already taken beside the
* archive would otherwise be overwritten here and deleted again
* on teardown, taking an unrelated file with it. A directory of
* our own makes the collision impossible instead of detecting it.
*
* The cache directory is the parent when one is configured;
* otherwise the archive's own directory is, which is the only
* location known to exist and be writable at this point. */
if (content_ctx->directory_cache && *content_ctx->directory_cache)
{
strlcpy(tmp_dir, content_ctx->directory_cache, sizeof(tmp_dir));
fill_pathname_slash(tmp_dir, sizeof(tmp_dir));
}
else
fill_pathname_basedir(tmp_dir, *content_path, sizeof(tmp_dir));
_len = strlen(tmp_dir);
/* First name not already on disk wins. A stale directory from a
* previous run - a crash between extraction and teardown - is
* therefore stepped over rather than reused, so its contents can
* never be mistaken for this load's content. */
for (i = 0; i < 1024; i++)
{
snprintf(tmp_dir + _len, sizeof(tmp_dir) - _len,
".extract-%u", i);
if (!path_is_valid(tmp_dir))
break;
}
if (i == 1024 || !path_mkdir(tmp_dir))
goto error;
/* Attempt to extract file */
if (!file_archive_extract_file(
*content_path, valid_exts, tmp_dir,
tmp_path, sizeof(tmp_path)))
{
/* Only ever removes the directory created just above, and it
* is empty on this path, so nothing else can be caught by it. */
filestream_delete(tmp_dir);
goto error;
}
/* 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;
/* The directory follows its own file in the list, so teardown
* empties it before removing it. */
content_file_list_append_temporary(p_content->content_list, tmp_dir);
/* 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;
error:
/* tmp_path is spent on this path - whatever the extraction left in
* it is unused - so it carries the message rather than a second
* buffer of its size sitting in the frame for the error case. */
snprintf(tmp_path, sizeof(tmp_path), "%s: \"%s\".\n",
msg_hash_to_str(MSG_FAILED_TO_EXTRACT_CONTENT_FROM_COMPRESSED_FILE),
*content_path);
*err_string = strdup(tmp_path);
return false;
}
#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)
{
bool path_is_archive;
bool path_is_inside_archive;
const char *content_path = content->elems[idx].data;
if (!content_path || !*content_path)
return;
#if defined(ANDROID) && defined(HAVE_SAF)
/* Convert content:// URIs to the path format used by the VFS */
if (strncmp(content_path, "content://", sizeof "content://" - 1) == 0)
{
struct libretro_vfs_implementation_saf_path_split_result result;
if (retro_vfs_path_split_content_saf(&result, content_path))
{
char *serialized_path = retro_vfs_path_join_saf(result.tree, result.path);
free(result.path);
free(result.tree);
if (serialized_path != NULL)
{
/* Store the serialized path in the content list entry
* itself, so it is freed when the list is freed and we
* avoid a static buffer leak. */
free(content->elems[idx].data);
content->elems[idx].data = serialized_path;
content_path = serialized_path;
}
}
}
#endif
#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->elems[idx].attr.i & BLCK_BLOCK_EXTRACT) != 0)
&& 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 (archive_file && *archive_file)
{
char info_path[PATH_MAX_LENGTH];
/* Build 'complete' archive file path */
size_t _len = strlcpy(info_path,
content_path, sizeof(info_path) - 2);
info_path[_len ] = '#';
info_path[_len+1] = '\0';
_len += 1;
strlcpy(info_path + _len, archive_file, sizeof(info_path) - _len);
/* Update 'content' string_list */
free(content->elems[idx].data);
content->elems[idx].data = strdup(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->elems[idx].attr.i & BLCK_BLOCK_EXTRACT) != 0);
bool required = ((content->elems[idx].attr.i & BLCK_REQUIRED) != 0);
bool persistent = ((content->elems[idx].attr.i & BLCK_PERSISTENT) != 0);
content->elems[idx].attr.i = 0;
/* 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) */
if (block_extract)
content->elems[idx].attr.i |= BLCK_BLOCK_EXTRACT;
if (override->need_fullpath)
content->elems[idx].attr.i |= BLCK_NEED_FULLPATH;
if (required)
content->elems[idx].attr.i |= BLCK_REQUIRED;
if (persistent || override->persistent_data)
content->elems[idx].attr.i |= BLCK_PERSISTENT;
}
}
/**
* 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 **err_string,
const struct retro_subsystem_info *special)
{
size_t i;
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;
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 (!content_path || !*content_path)
{
if ((content->elems[i].attr.i & BLCK_REQUIRED) != 0)
{
*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 */
/* Doesn't need fullpath? */
if (!((content->elems[i].attr.i & BLCK_NEED_FULLPATH) != 0))
{
content_data = NULL;
if ((content_size = content_file_load_into_memory(
content_ctx, p_content, content_path,
content_compressed, i, first_content_type,
&content_data)) == 0)
{
char msg[PATH_MAX_LENGTH];
snprintf(msg, sizeof(msg), "%s: \"%s\".\n",
msg_hash_to_str(MSG_COULD_NOT_READ_CONTENT_FILE),
content_path);
*err_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->elems[i].attr.i & BLCK_BLOCK_EXTRACT) != 0)
&& !content_file_extract_from_archive(content_ctx, p_content,
valid_exts, &content_path, err_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[DIR_MAX_LENGTH];
char new_path[PATH_MAX_LENGTH];
RARCH_LOG("[Content] Core does not support VFS"
" - copying to cache directory...\n");
if (content_ctx->directory_cache && *content_ctx->directory_cache)
strlcpy(new_basedir, content_ctx->directory_cache,
sizeof(new_basedir));
else
new_basedir[0] = '\0';
if ( (!new_basedir || !*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_lit(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))
{
char msg[PATH_MAX_LENGTH];
/* 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);
*err_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)
{
}
}
}
/* Add current entry to content file list */
if (!content_file_list_set_info(
p_content->content_list,
content_path, content_data, content_size,
((content->elems[i].attr.i & BLCK_PERSISTENT) != 0), 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 (first_content_path && *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 **err_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));
if (!special)
{
char msg[128];
/* TODO/FIXME - localize */
snprintf(msg, sizeof(msg),
"Failed to find subsystem \"%s\" in libretro implementation.\n",
path_get(RARCH_PATH_SUBSYSTEM));
*err_string = strdup(msg);
*ret = false;
return NULL;
}
if (special->num_roms)
{
if (!subsystem)
{
*error_enum = MSG_ERROR_LIBRETRO_CORE_REQUIRES_SPECIAL_CONTENT;
*ret = false;
return NULL;
}
if (special->num_roms != subsystem->size)
{
char msg[128];
/* 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);
*err_string = strdup(msg);
*ret = false;
return NULL;
}
}
else if (subsystem && subsystem->size)
{
char msg[128];
/* 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);
*err_string = strdup(msg);
*ret = false;
return NULL;
}
*ret = true;
return special;
}
static void content_file_set_attributes(
struct string_list *content,
const struct retro_subsystem_info *special,
content_information_ctx_t *content_ctx)
{
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;
attr.i = 0;
if (special->roms[i].block_extract)
attr.i |= BLCK_BLOCK_EXTRACT;
if (special->roms[i].need_fullpath)
attr.i |= BLCK_NEED_FULLPATH;
if (special->roms[i].required)
attr.i |= BLCK_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();
attr.i = 0;
if (content_ctx->flags & CONTENT_INFO_FLAG_BLOCK_EXTRACT)
attr.i |= BLCK_BLOCK_EXTRACT;
if (content_ctx->flags & CONTENT_INFO_FLAG_NEED_FULLPATH)
attr.i |= BLCK_NEED_FULLPATH;
if (!(flags
& CONTENT_ST_FLAG_CORE_DOES_NOT_NEED_CONTENT))
attr.i |= BLCK_REQUIRED;
/* 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) */
#if defined(HAVE_RUNAHEAD)
attr.i |= BLCK_PERSISTENT;
#endif
if (!content_path || !*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_n(content, "", STRLEN_CONST(""), 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 **err_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, err_string, &ret);
if (!ret)
return false;
content_file_set_attributes(content, special, content_ctx);
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, err_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)
{
size_t i;
bool ret = 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;
/* The following snippet breaks command-line arguments on Haiku which in turn
prevents from using RA without a menu or to start it from a front-end like ES-DE.
All things considered, the risk/reward is favorable to just skipping this. */
#ifndef __HAIKU__
if (info->environ_get)
info->environ_get(rarch_argc_ptr,
rarch_argv_ptr, info->args, wrap_args);
#endif
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;
ret = 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 (!ret)
return false;
if (p_content->flags & CONTENT_ST_FLAG_PENDING_SUBSYSTEM_INIT)
content_clear_subsystem();
#ifdef HAVE_GFX_WIDGETS
#ifdef HAVE_CONFIGFILE
/* If retroarch_main_init() returned true, we
* can safely trigger a load content animation.
*
* Unless the deferred path already started one before the read,
* so it could carry the read percentage: restarting it here would
* replay the card the user has been watching. Clear the
* percentage instead - the read is over - and leave it be. */
if (gfx_widgets_ready())
{
if (content_load_animation_showing)
{
gfx_widget_set_load_content_progress(-1);
content_load_animation_showing = false;
}
else
{
/* 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
#ifdef HAVE_MENU
retroarch_favorites_init();
command_event(CMD_EVENT_HISTORY_INIT, NULL);
#endif
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)
{
const char *a = NULL;
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);
a = dir_get_ptr(RARCH_DIR_SAVEFILE);
if (a && *a)
wrap_args->sram_path = dir_get_ptr(RARCH_DIR_SAVEFILE);
a = dir_get_ptr(RARCH_DIR_SAVESTATE);
if (a && *a)
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))
{
a = path_get(RARCH_PATH_CORE);
wrap_args->libretro_path = (a && *a) ? a : NULL;
}
}
/**
* 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 (path_content && *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 && *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 (!core_name || !*core_name)
core_name = sysinfo->library_name;
if (launched_from_companion_ui)
{
/* Database name + checksum are supplied
* by the companion UI itself */
if (*p_content->companion_ui_crc32)
crc32 = p_content->companion_ui_crc32;
if (*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 (*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
|| (!content_path || !*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 ( (core && *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 ( (core_path && *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 (data && *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
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;
if (!content_info)
return false;
content_information_ctx_init(&content_ctx, settings, runloop_st, false);
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);
content_information_ctx_free(&content_ctx);
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;
#ifndef HAVE_DYNAMIC
bool force_core_reload = settings->bools.always_reload_core_on_run_content;
#endif
content_information_ctx_init(&content_ctx, settings, runloop_st, false);
if (label)
strlcpy(runloop_st->name.label, label, sizeof(runloop_st->name.label));
else
runloop_st->name.label[0] = '\0';
/* Is content required by this core? */
sys_info->load_no_content = fullpath ? false : 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 (fullpath && *fullpath)
path_set(RARCH_PATH_CONTENT, fullpath);
/* Load content and update content history */
if ((ret = content_load(content_info, p_content)))
task_push_to_history_list(p_content, true, false, false);
goto end;
}
#endif
/* Specified core is not loaded
* > Load it
* > Forget manually loaded core */
path_set(RARCH_PATH_CORE, core_path);
path_clear(RARCH_PATH_CORE_LAST);
#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)))
{
#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
}
#ifndef HAVE_DYNAMIC
end:
#endif
/* Handle load content failure */
if (!ret)
retroarch_menu_running();
content_information_ctx_free(&content_ctx);
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();
if (!content_info)
return false;
content_information_ctx_init(&content_ctx, settings, runloop_st, false);
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);
/* 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:
content_information_ctx_free(&content_ctx);
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)
{
/* Set core path */
path_set(RARCH_PATH_CORE, core_path);
/* Remember core path for reloading */
if (!string_is_equal(core_path, path_get(RARCH_PATH_CORE_LAST)))
path_set(RARCH_PATH_CORE_LAST, 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;
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();
bool flush_menu = true;
const char *menu_label = NULL;
if (!core_path || !*core_path)
return false;
content_information_ctx_init(&content_ctx, settings, runloop_st, false);
/* 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);
/* 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, MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB_STR)
|| string_is_equal(menu_label, MENU_ENUM_LABEL_DEFERRED_CONTENTLESS_CORES_LIST_STR))
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
content_information_ctx_free(&content_ctx);
return ret;
}
#if defined(HAVE_DYNAMIC) && defined(HAVE_MENU)
/* ---- deferred menu load: prefetch, then the identical remainder ---- */
struct content_deferred_menu_load
{
char *fullpath;
char *core_path; /* the world this belongs to */
enum rarch_core_type type;
content_ctx_info_t info; /* argv-free by the deferral gate */
bool showed_animation; /* launch card already on screen */
};
/* The continuation parked by the prefetch's done callback, consumed
* by task_content_deferred_load_check() from the runloop. One
* deferral at a time (the CONTENT_ST_FLAG_DEFERRED_LOAD_PENDING
* gate), so a single pointer is the whole queue. */
static struct content_deferred_menu_load *deferred_menu_load_ready = NULL;
/* Fires when the prefetch task completes. The remainder of the
* load cannot run here: content_load() reaches
* retroarch_init_task_queue(), which tears down and recreates the
* task queue - fatal from inside the queue's own dispatch (and,
* under the threaded queue, this may not be the main thread). So
* the continuation is parked, and runloop_iterate() performs it at
* the top of the next frame - the same call depth the synchronous
* path ran it from. */
static void task_content_deferred_menu_load_done(void *ud, bool all_ok)
{
deferred_menu_load_ready = (struct content_deferred_menu_load*)ud;
}
/* Drop whatever the load did not consume. */
static void content_file_prefetch_free(content_state_t *p_content)
{
size_t i;
for (i = 0; i < p_content->prefetch_count; i++)
{
free(p_content->prefetch[i].path);
free(p_content->prefetch[i].data);
p_content->prefetch[i].path = NULL;
p_content->prefetch[i].data = NULL;
p_content->prefetch[i].size = 0;
}
p_content->prefetch_count = 0;
}
/* Called once per frame from runloop_iterate(): performs the
* remainder of task_push_load_content_with_new_core_from_menu for a
* completed prefetch, byte-for-byte the sequence the synchronous
* path runs - content_load, history, quick menu. A prefetch that
* skipped files changed nothing; the load's ordinary reads cover
* whatever is not in the cache. */
void task_content_deferred_load_check(void)
{
struct content_deferred_menu_load *d = deferred_menu_load_ready;
content_state_t *p_content;
if (!d)
return;
deferred_menu_load_ready = NULL;
p_content = content_state_get_ptr();
p_content->flags &= ~CONTENT_ST_FLAG_DEFERRED_LOAD_PENDING;
/* A competing load may have replaced the world this continuation
* was parked for; the stamps say which one it belongs to. On a
* mismatch, stand down: drop the cache and the continuation, and
* leave the current content alone. */
if ( !string_is_equal(path_get(RARCH_PATH_CONTENT), d->fullpath)
|| !string_is_equal(path_get(RARCH_PATH_CORE), d->core_path))
{
RARCH_LOG("[Content] Dropping a deferred load superseded by "
"another: \"%s\".\n", d->fullpath);
content_file_prefetch_free(p_content);
free(d->core_path);
free(d->fullpath);
free(d);
return;
}
if (!content_load(&d->info, p_content))
{
content_file_prefetch_free(p_content);
retroarch_menu_running();
}
else
{
task_push_to_history_list(p_content, true, false, false);
if (d->type != CORE_TYPE_DUMMY)
menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUICK_MENU, NULL);
}
content_file_prefetch_free(p_content); /* leftovers, if any */
free(d->core_path);
free(d->fullpath);
free(d);
}
#if defined(HAVE_GFX_WIDGETS)
/* Feeds the read percentage to the "Load Content" startup
* notification. Delivered on the thread that pumps the queue -
* the thread that drives the frame and owns widget state. */
static void content_file_prefetch_progress(void *ud, int8_t progress)
{
gfx_widget_set_load_content_progress(progress);
}
#endif
/* Deposit callback for task_push_content_prefetch. ud carries the
* deferred-load continuation for the done callback, not the content
* state - the state is a singleton and is fetched here, never cast
* from ud. */
static void content_file_prefetch_deposit(void *ud, const char *path,
uint8_t *data, size_t size)
{
content_state_t *p_content = content_state_get_ptr();
if (p_content->prefetch_count
>= ARRAY_SIZE(p_content->prefetch))
{
free(data);
return;
}
if (!(p_content->prefetch[p_content->prefetch_count].path
= strdup(path)))
{
free(data);
return;
}
p_content->prefetch[p_content->prefetch_count].data = data;
p_content->prefetch[p_content->prefetch_count].size = size;
p_content->prefetch_count++;
}
/* Returns true when the load was taken over by the deferred path. */
static bool task_content_defer_menu_load(content_state_t *p_content,
runloop_state_t *runloop_st,
const char *fullpath, enum rarch_core_type type,
content_ctx_info_t *content_info)
{
struct content_deferred_menu_load *d = NULL;
const char *paths[1];
if (type != CORE_TYPE_PLAIN)
return false;
if (!fullpath || !*fullpath)
return false; /* contentless: nothing to read */
if (content_info->argc || content_info->argv || content_info->args)
return false; /* only the plain menu shape */
if (p_content->flags & CONTENT_ST_FLAG_DEFERRED_LOAD_PENDING)
return false; /* one deferral at a time */
/* The cache's only consumer is content_file_load_into_memory(),
* reached only for a file the load reads into memory. A
* need_fullpath core is handed the path instead, so a prefetch
* would fill an allocation nothing takes. Mirror the load's own
* BLCK_NEED_FULLPATH decision from the live system info and the
* per-extension override - both current here, LOAD_CORE has run.
* (The caller's content_ctx is built before LOAD_CORE and without
* sys info; it cannot answer this.) */
{
const content_file_override_t *override = NULL;
bool need_fullpath = runloop_st->system.info.need_fullpath;
if (content_file_override_get_ext(p_content,
path_get_extension(fullpath), &override))
need_fullpath = override->need_fullpath;
if (need_fullpath)
return false; /* the load hands the core a path */
}
/* The prefetch keys on this exact path, but the load rewrites
* some paths before reading them: a content:// SAF URI becomes a
* VFS path, and a bare archive ("foo.zip") becomes an explicit
* entry ("foo.zip#first"). Deferring either would prefetch under
* a key the load never looks up - a silent miss and a wasted
* read. Decline them; they take the synchronous path, exactly as
* before this feature existed. A path already carrying its entry
* ("foo.zip#rom") is stable and may defer. */
if (!strncmp(fullpath, "content://", STRLEN_CONST("content://")))
return false;
if ( path_is_compressed_file(fullpath)
&& !path_contains_compressed_file(fullpath))
return false; /* bare archive: load picks entry */
if (!(d = (struct content_deferred_menu_load*)calloc(1, sizeof(*d))))
return false;
if (!(d->fullpath = strdup(fullpath)))
{
free(d);
return false;
}
/* Stamp the world this continuation belongs to: in-flight tasks
* survive a competing load's queue reinit, so the continuation
* can fire after the user has loaded something else. */
if (!(d->core_path = strdup(path_get(RARCH_PATH_CORE))))
{
free(d->fullpath);
free(d);
return false;
}
d->type = type;
d->info = *content_info; /* argv-free: shallow is whole */
paths[0] = d->fullpath;
#if defined(HAVE_GFX_WIDGETS)
/* Start the launch notification here rather than after the load,
* so it can carry the read percentage while the content streams
* in. Gated on the same setting as before: with the notification
* off, nothing is shown and no progress is reported.
*
* Widgets persist across the driver reinit the load performs
* (DISPGFX_WIDGET_FLAG_PERSISTING), so the card started here
* survives into the loaded core. */
{
settings_t *settings = config_get_ptr();
if ( settings
&& settings->bools.menu_show_load_content_animation
&& gfx_widgets_ready())
{
gfx_widget_set_load_content_progress(-1);
if (gfx_widget_start_load_content_animation())
d->showed_animation = true;
}
}
#endif
if (!task_push_content_prefetch_progress(paths, 1,
content_file_prefetch_deposit,
task_content_deferred_menu_load_done,
#if defined(HAVE_GFX_WIDGETS)
d->showed_animation ? content_file_prefetch_progress : NULL,
#else
NULL,
#endif
d))
{
free(d->core_path);
free(d->fullpath);
free(d);
return false;
}
p_content->flags |= CONTENT_ST_FLAG_DEFERRED_LOAD_PENDING;
return true;
}
#endif
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();
#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_information_ctx_init(&content_ctx, settings, runloop_st, false);
runloop_st->name.label[0] = '\0';
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;
/* Stream the content's bytes in ahead of the load, a budgeted
* slice per task tick, so the menu keeps running instead of
* freezing for one long read. The deferral is taken only in the
* exact shape this menu path produces (no argv, plain core type,
* no deferral already in flight); anything else keeps the
* synchronous path below, and if the prefetch cannot even be
* pushed, so does everything. The continuation performs the
* identical remainder of this function. */
if (task_content_defer_menu_load(p_content, runloop_st,
fullpath, type, content_info))
{
content_information_ctx_free(&content_ctx);
return true;
}
/* 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
content_information_ctx_free(&content_ctx);
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();
settings_t *settings = config_get_ptr();
content_information_ctx_init(&content_ctx, settings, runloop_st, true);
if (!content_info->environ_get)
content_info->environ_get = menu_content_environment_get;
#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);
content_information_ctx_free(&content_ctx);
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))
{
#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;
}
#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 (db_name && *db_name)
strlcpy(p_content->companion_ui_db_name,
db_name, sizeof(p_content->companion_ui_db_name));
if (crc32 && *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->flags &= ~GLOB_FLG_LAUNCHED_FROM_CLI;
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,
const char *label,
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();
runloop_state_t *runloop_st = runloop_state_get_ptr();
/* The content label is global state that survives until
* the next content load overwrites it, and it is what
* task_push_to_history_list() writes into the history
* playlist entry. Every other load path either sets it
* or clears it; this one did neither, so a subsystem
* launch inherited - and recorded - the label belonging
* to whatever content was loaded before it. */
if (label && *label)
strlcpy(runloop_st->name.label, label,
sizeof(runloop_st->name.label));
else
runloop_st->name.label[0] = '\0';
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)
{
size_t 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)
{
unsigned i;
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))
{
content_set_subsystem(i);
return true;
}
}
return false;
}
size_t content_get_subsystem_friendly_name(const char* subsystem_name, char *s, size_t len)
{
size_t i;
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))
return strlcpy(s, subsystem->desc, len);
}
return 0;
}
/* 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);
char *rom_buf = NULL;
if (p_content->pending_subsystem_rom_id >= RARCH_MAX_SUBSYSTEM_ROMS)
{
RARCH_ERR("[Subsystem] Cannot add ROM - maximum subsystem ROM count reached.\n");
return;
}
if (!(rom_buf = (char*)malloc(pending_size)))
{
RARCH_ERR("[Subsystem] Failed to allocate memory for subsystem ROM path.\n");
return;
}
p_content->pending_subsystem_roms[p_content->pending_subsystem_rom_id] = rom_buf;
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;
}
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->flags &= ~(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 *err_string = NULL;
runloop_state_t *runloop_st = runloop_state_get_ptr();
settings_t *settings = config_get_ptr();
content_file_list_free(p_content->content_list);
p_content->content_list = NULL;
content_information_ctx_init(&content_ctx, settings, runloop_st, true);
p_content->flags |= CONTENT_ST_FLAG_IS_INITED;
if (string_list_initialize(&content))
{
if (!content_file_init(&content_ctx, p_content,
&content, &error_enum, &err_string))
{
content_deinit();
ret = false;
}
string_list_deinitialize(&content);
}
content_information_ctx_free(&content_ctx);
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:
{
const char *_msg = msg_hash_to_str(error_enum);
RARCH_ERR("[Content] %s\n", _msg);
runloop_msg_queue_push(_msg, strlen(_msg), 2, ret ? 1 : 180, false, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
}
break;
case MSG_UNKNOWN:
default:
break;
}
}
if (err_string)
{
/* Trim ending newline */
size_t err_len = strlen(err_string);
if (err_string[err_len - 1] == '\n')
err_string[err_len - 1] = '\0';
if (ret)
RARCH_LOG("[Content] %s\n", err_string);
else
RARCH_ERR("[Content] %s\n", err_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(err_string, strlen(err_string), 2, ret ? 1 : 180, false, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
free(err_string);
}
return ret;
}