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