mirror of
https://github.com/libretro/RetroArch
synced 2024-12-28 09:29:16 +00:00
51922ea5be
2934af8af0 Added Patreon sponsor link. c8f18b6f0f Getting current program only when required for vglDrawObjects. 4c5d136b0d Added directive to enable vitaShaRK usage from cmd. 4a10df3be5 Minor adjustments and bugfixes. 14a0124acf Added GL_TEXTURE_LOD_BIAS support. 40c8c6205e Added GL_NONE def and fixed glUniform4f impl. 868079c51e Added glUniform4f implementation. 0a682cbad2 Typo fix. be3ce61ae7 Added GL_DEPTH_BITS and GL_STENCIL_BITS support. 21e6d1d330 Added runtime shader compiler support. 696e40bc62 Beautify error handler code. 537b37b110 Added glUniform3fv implementation. 7dd1403015 Fixed GLenum size and added missing types defines. 0c75f27ff1 Moved to NEON optimized memcpy usage. 98951895de Added gluPerspective implementation. 23e0b0b309 Fix for vglInitExtended not working on sys app mode. 4989c33ef5 Run clang-format. 429f1c1d8a Added system mode support. 9231680d02 Initializing sceGxm before free mem checking on vglInitExtended. 091e5e7882 Added vglInitWithCustomSizes. f4c646ea78 Added vglSetParamBufferSize. 1b9a063c41 Beautify some code. 089e81efc5 Fix for duplicated symbols 789dcbf812 Typo fix in readRGBA4444. 1514a4b2cb Disabling lto due to it being broken on vitasdk with gcc 9.1. fca18d9ab7 Added support for RGBA4444 texture format. d449f12808 Added support for RGB565 texture format. git-subtree-dir: deps/vitaGL git-subtree-split: 2934af8af083a9acf598ab75233c518a251c6f0d
1649 lines
46 KiB
C
1649 lines
46 KiB
C
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
|
|
* Copyright (C) 2011-2017 - Daniel De Matteis
|
|
* Copyright (C) 2016-2019 - Brad Parker
|
|
*
|
|
* 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/>.
|
|
*/
|
|
|
|
#include <compat/strl.h>
|
|
#include <string/stdstring.h>
|
|
#include <file/config_file.h>
|
|
#include <file/file_path.h>
|
|
#include <streams/file_stream.h>
|
|
#include <lists/dir_list.h>
|
|
#include <file/archive_file.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "retroarch.h"
|
|
|
|
#include "core_info.h"
|
|
#include "file_path_special.h"
|
|
|
|
#if defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
|
|
#include "uwp/uwp_func.h"
|
|
#endif
|
|
|
|
enum compare_op
|
|
{
|
|
COMPARE_OP_EQUAL = 0,
|
|
COMPARE_OP_NOT_EQUAL,
|
|
COMPARE_OP_LESS,
|
|
COMPARE_OP_LESS_EQUAL,
|
|
COMPARE_OP_GREATER,
|
|
COMPARE_OP_GREATER_EQUAL
|
|
};
|
|
|
|
static void core_info_list_resolve_all_extensions(
|
|
core_info_list_t *core_info_list)
|
|
{
|
|
size_t i = 0;
|
|
size_t all_ext_len = 0;
|
|
char *all_ext = NULL;
|
|
|
|
for (i = 0; i < core_info_list->count; i++)
|
|
{
|
|
if (core_info_list->list[i].supported_extensions)
|
|
all_ext_len +=
|
|
(strlen(core_info_list->list[i].supported_extensions) + 2);
|
|
}
|
|
|
|
all_ext_len += STRLEN_CONST("7z|") + STRLEN_CONST("zip|");
|
|
|
|
all_ext = (char*)calloc(1, all_ext_len);
|
|
|
|
if (!all_ext)
|
|
return;
|
|
|
|
core_info_list->all_ext = all_ext;
|
|
|
|
for (i = 0; i < core_info_list->count; i++)
|
|
{
|
|
if (!core_info_list->list[i].supported_extensions)
|
|
continue;
|
|
|
|
strlcat(core_info_list->all_ext,
|
|
core_info_list->list[i].supported_extensions, all_ext_len);
|
|
strlcat(core_info_list->all_ext, "|", all_ext_len);
|
|
}
|
|
#ifdef HAVE_7ZIP
|
|
strlcat(core_info_list->all_ext, "7z|", all_ext_len);
|
|
#endif
|
|
#ifdef HAVE_ZLIB
|
|
strlcat(core_info_list->all_ext, "zip|", all_ext_len);
|
|
#endif
|
|
}
|
|
|
|
static void core_info_list_resolve_all_firmware(
|
|
core_info_list_t *core_info_list)
|
|
{
|
|
size_t i;
|
|
unsigned c;
|
|
|
|
for (i = 0; i < core_info_list->count; i++)
|
|
{
|
|
unsigned count = 0;
|
|
core_info_firmware_t *firmware = NULL;
|
|
core_info_t *info = (core_info_t*)&core_info_list->list[i];
|
|
config_file_t *config = (config_file_t*)info->config_data;
|
|
|
|
if (!config || !config_get_uint(config, "firmware_count", &count))
|
|
continue;
|
|
|
|
firmware = (core_info_firmware_t*)calloc(count, sizeof(*firmware));
|
|
|
|
if (!firmware)
|
|
continue;
|
|
|
|
info->firmware = firmware;
|
|
|
|
for (c = 0; c < count; c++)
|
|
{
|
|
char path_key[64];
|
|
char desc_key[64];
|
|
char opt_key[64];
|
|
struct config_entry_list
|
|
*entry = NULL;
|
|
bool tmp_bool = false;
|
|
path_key[0] = desc_key[0] = opt_key[0] = '\0';
|
|
|
|
snprintf(path_key, sizeof(path_key), "firmware%u_path", c);
|
|
snprintf(desc_key, sizeof(desc_key), "firmware%u_desc", c);
|
|
snprintf(opt_key, sizeof(opt_key), "firmware%u_opt", c);
|
|
|
|
entry = config_get_entry(config, path_key, NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
info->firmware[c].path = strdup(entry->value);
|
|
|
|
entry = config_get_entry(config, desc_key, NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
info->firmware[c].desc = strdup(entry->value);
|
|
|
|
if (config_get_bool(config, opt_key , &tmp_bool))
|
|
info->firmware[c].optional = tmp_bool;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void core_info_list_free(core_info_list_t *core_info_list)
|
|
{
|
|
size_t i, j;
|
|
|
|
if (!core_info_list)
|
|
return;
|
|
|
|
for (i = 0; i < core_info_list->count; i++)
|
|
{
|
|
core_info_t *info = (core_info_t*)&core_info_list->list[i];
|
|
|
|
free(info->path);
|
|
free(info->core_name);
|
|
free(info->systemname);
|
|
free(info->system_id);
|
|
free(info->system_manufacturer);
|
|
free(info->display_name);
|
|
free(info->display_version);
|
|
free(info->supported_extensions);
|
|
free(info->authors);
|
|
free(info->permissions);
|
|
free(info->licenses);
|
|
free(info->categories);
|
|
free(info->databases);
|
|
free(info->notes);
|
|
free(info->required_hw_api);
|
|
free(info->description);
|
|
string_list_free(info->supported_extensions_list);
|
|
string_list_free(info->authors_list);
|
|
string_list_free(info->note_list);
|
|
string_list_free(info->permissions_list);
|
|
string_list_free(info->licenses_list);
|
|
string_list_free(info->categories_list);
|
|
string_list_free(info->databases_list);
|
|
string_list_free(info->required_hw_api_list);
|
|
config_file_free((config_file_t*)info->config_data);
|
|
|
|
for (j = 0; j < info->firmware_count; j++)
|
|
{
|
|
free(info->firmware[j].path);
|
|
free(info->firmware[j].desc);
|
|
}
|
|
free(info->firmware);
|
|
|
|
free(info->core_file_id.str);
|
|
}
|
|
|
|
free(core_info_list->all_ext);
|
|
free(core_info_list->list);
|
|
free(core_info_list);
|
|
}
|
|
|
|
static config_file_t *core_info_list_iterate(
|
|
const char *current_path,
|
|
const char *path_basedir)
|
|
{
|
|
size_t info_path_base_size = PATH_MAX_LENGTH * sizeof(char);
|
|
char *info_path_base = NULL;
|
|
char *info_path = NULL;
|
|
config_file_t *conf = NULL;
|
|
|
|
if (!current_path)
|
|
return NULL;
|
|
|
|
info_path_base = (char*)malloc(info_path_base_size);
|
|
|
|
info_path_base[0] = '\0';
|
|
|
|
fill_pathname_base_noext(info_path_base,
|
|
current_path,
|
|
info_path_base_size);
|
|
|
|
#if defined(RARCH_MOBILE) || (defined(RARCH_CONSOLE) && !defined(PSP) && !defined(_3DS) && !defined(VITA) && !defined(HW_WUP))
|
|
{
|
|
char *substr = strrchr(info_path_base, '_');
|
|
if (substr)
|
|
*substr = '\0';
|
|
}
|
|
#endif
|
|
|
|
strlcat(info_path_base, ".info", info_path_base_size);
|
|
|
|
info_path = (char*)malloc(info_path_base_size);
|
|
fill_pathname_join(info_path,
|
|
path_basedir,
|
|
info_path_base, info_path_base_size);
|
|
free(info_path_base);
|
|
info_path_base = NULL;
|
|
|
|
if (path_is_valid(info_path))
|
|
conf = config_file_new_from_path_to_string(info_path);
|
|
free(info_path);
|
|
|
|
return conf;
|
|
}
|
|
|
|
/* Returned path must be free()'d */
|
|
static char *core_info_get_core_lock_file_path(const char *core_path)
|
|
{
|
|
char *lock_file_path = NULL;
|
|
const char *lock_file_ext = file_path_str(FILE_PATH_LOCK_EXTENSION);
|
|
size_t len;
|
|
|
|
if (string_is_empty(core_path))
|
|
return NULL;
|
|
|
|
/* Note: We follow the common 'core_info' trend of
|
|
* allocating all strings dynamically... */
|
|
|
|
/* Get path length */
|
|
len = (strlen(core_path) + strlen(lock_file_ext) + 1) * sizeof(char);
|
|
|
|
/* Allocate string */
|
|
lock_file_path = (char*)malloc(len);
|
|
if (!lock_file_path)
|
|
return NULL;
|
|
|
|
lock_file_path[0] = '\0';
|
|
|
|
/* Lock file is just core path + 'lock' extension */
|
|
strlcpy(lock_file_path, core_path, len);
|
|
strlcat(lock_file_path, lock_file_ext, len);
|
|
|
|
return lock_file_path;
|
|
}
|
|
|
|
static core_info_list_t *core_info_list_new(const char *path,
|
|
const char *libretro_info_dir,
|
|
const char *exts,
|
|
bool dir_show_hidden_files)
|
|
{
|
|
size_t i;
|
|
core_info_t *core_info = NULL;
|
|
core_info_list_t *core_info_list = NULL;
|
|
const char *path_basedir = libretro_info_dir;
|
|
struct string_list *contents = string_list_new();
|
|
bool ok = dir_list_append(contents, path, exts,
|
|
false, dir_show_hidden_files, false, false);
|
|
|
|
#if defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
|
|
/* UWP: browse the optional packages for additional cores */
|
|
struct string_list *core_packages = string_list_new();
|
|
uwp_fill_installed_core_packages(core_packages);
|
|
for (i = 0; i < core_packages->size; i++)
|
|
dir_list_append(contents, core_packages->elems[i].data, exts,
|
|
false, dir_show_hidden_files, false, false);
|
|
string_list_free(core_packages);
|
|
#else
|
|
/* Keep the old 'directory not found' behavior */
|
|
if (!ok)
|
|
{
|
|
string_list_free(contents);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
if (!contents)
|
|
return NULL;
|
|
|
|
core_info_list = (core_info_list_t*)malloc(sizeof(*core_info_list));
|
|
if (!core_info_list)
|
|
{
|
|
string_list_free(contents);
|
|
return NULL;
|
|
}
|
|
|
|
core_info_list->list = NULL;
|
|
core_info_list->count = 0;
|
|
core_info_list->all_ext = NULL;
|
|
|
|
core_info = (core_info_t*)
|
|
calloc(contents->size, sizeof(*core_info));
|
|
|
|
if (!core_info)
|
|
{
|
|
core_info_list_free(core_info_list);
|
|
string_list_free(contents);
|
|
return NULL;
|
|
}
|
|
|
|
core_info_list->list = core_info;
|
|
core_info_list->count = contents->size;
|
|
|
|
for (i = 0; i < contents->size; i++)
|
|
{
|
|
const char *base_path = contents->elems[i].data;
|
|
config_file_t *conf = core_info_list_iterate(base_path,
|
|
path_basedir);
|
|
|
|
if (conf)
|
|
{
|
|
bool tmp_bool = false;
|
|
unsigned tmp_uint = 0;
|
|
struct config_entry_list
|
|
*entry = config_get_entry(conf, "display_name", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
core_info[i].display_name = strdup(entry->value);
|
|
|
|
entry = config_get_entry(conf, "display_version", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
core_info[i].display_version = strdup(entry->value);
|
|
|
|
entry = config_get_entry(conf, "corename", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
core_info[i].core_name = strdup(entry->value);
|
|
|
|
entry = config_get_entry(conf, "systemname", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
core_info[i].systemname = strdup(entry->value);
|
|
|
|
entry = config_get_entry(conf, "systemid", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
core_info[i].system_id = strdup(entry->value);
|
|
|
|
entry = config_get_entry(conf, "manufacturer", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
core_info[i].system_manufacturer = strdup(entry->value);
|
|
|
|
config_get_uint(conf, "firmware_count", &tmp_uint);
|
|
core_info[i].firmware_count = tmp_uint;
|
|
|
|
entry = config_get_entry(conf, "supported_extensions", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
{
|
|
core_info[i].supported_extensions = strdup(entry->value);
|
|
core_info[i].supported_extensions_list =
|
|
string_split(core_info[i].supported_extensions, "|");
|
|
}
|
|
|
|
entry = config_get_entry(conf, "authors", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
{
|
|
core_info[i].authors = strdup(entry->value);
|
|
core_info[i].authors_list =
|
|
string_split(core_info[i].authors, "|");
|
|
}
|
|
|
|
entry = config_get_entry(conf, "permissions", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
{
|
|
core_info[i].permissions = strdup(entry->value);
|
|
core_info[i].permissions_list =
|
|
string_split(core_info[i].permissions, "|");
|
|
}
|
|
|
|
entry = config_get_entry(conf, "license", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
{
|
|
core_info[i].licenses = strdup(entry->value);
|
|
core_info[i].licenses_list =
|
|
string_split(core_info[i].licenses, "|");
|
|
}
|
|
|
|
entry = config_get_entry(conf, "categories", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
{
|
|
core_info[i].categories = strdup(entry->value);
|
|
core_info[i].categories_list =
|
|
string_split(core_info[i].categories, "|");
|
|
}
|
|
|
|
entry = config_get_entry(conf, "database", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
{
|
|
core_info[i].databases = strdup(entry->value);
|
|
core_info[i].databases_list =
|
|
string_split(core_info[i].databases, "|");
|
|
}
|
|
|
|
entry = config_get_entry(conf, "notes", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
{
|
|
core_info[i].notes = strdup(entry->value);
|
|
core_info[i].note_list =
|
|
string_split(core_info[i].notes, "|");
|
|
}
|
|
|
|
entry = config_get_entry(conf, "required_hw_api", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
{
|
|
core_info[i].required_hw_api = strdup(entry->value);
|
|
core_info[i].required_hw_api_list =
|
|
string_split(core_info[i].required_hw_api, "|");
|
|
}
|
|
|
|
entry = config_get_entry(conf, "description", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
core_info[i].description = strdup(entry->value);
|
|
|
|
if (config_get_bool(conf, "supports_no_game",
|
|
&tmp_bool))
|
|
core_info[i].supports_no_game = tmp_bool;
|
|
|
|
if (config_get_bool(conf, "database_match_archive_member",
|
|
&tmp_bool))
|
|
core_info[i].database_match_archive_member = tmp_bool;
|
|
|
|
if (config_get_bool(conf, "is_experimental",
|
|
&tmp_bool))
|
|
core_info[i].is_experimental = tmp_bool;
|
|
|
|
core_info[i].config_data = conf;
|
|
}
|
|
|
|
if (!string_is_empty(base_path))
|
|
{
|
|
const char *core_filename = path_basename(base_path);
|
|
|
|
/* Cache core path */
|
|
core_info[i].path = strdup(base_path);
|
|
|
|
/* Cache core file 'id'
|
|
* > Filename without extension or platform-specific suffix */
|
|
if (!string_is_empty(core_filename))
|
|
{
|
|
char *core_file_id = strdup(core_filename);
|
|
path_remove_extension(core_file_id);
|
|
|
|
if (!string_is_empty(core_file_id))
|
|
{
|
|
#if defined(RARCH_MOBILE) || (defined(RARCH_CONSOLE) && !defined(PSP) && !defined(_3DS) && !defined(VITA) && !defined(HW_WUP))
|
|
char *last_underscore = strrchr(core_file_id, '_');
|
|
if (last_underscore)
|
|
*last_underscore = '\0';
|
|
#endif
|
|
core_info[i].core_file_id.str = core_file_id;
|
|
core_info[i].core_file_id.len = strlen(core_file_id);
|
|
|
|
core_file_id = NULL;
|
|
}
|
|
|
|
if (core_file_id)
|
|
{
|
|
free(core_file_id);
|
|
core_file_id = NULL;
|
|
}
|
|
|
|
/* Get fallback display name, if required */
|
|
if (!core_info[i].display_name)
|
|
core_info[i].display_name = strdup(core_filename);
|
|
}
|
|
}
|
|
|
|
/* Get core lock status */
|
|
core_info[i].is_locked = core_info_get_core_lock(core_info[i].path, false);
|
|
}
|
|
|
|
if (core_info_list)
|
|
{
|
|
core_info_list_resolve_all_extensions(core_info_list);
|
|
core_info_list_resolve_all_firmware(core_info_list);
|
|
}
|
|
|
|
string_list_free(contents);
|
|
return core_info_list;
|
|
}
|
|
|
|
/* Shallow-copies internal state.
|
|
*
|
|
* Data in *info is invalidated when the
|
|
* core_info_list is freed. */
|
|
bool core_info_list_get_info(core_info_list_t *core_info_list,
|
|
core_info_t *out_info, const char *path)
|
|
{
|
|
size_t i;
|
|
const char *core_filename = NULL;
|
|
|
|
if (!core_info_list || !out_info || string_is_empty(path))
|
|
return false;
|
|
|
|
core_filename = path_basename(path);
|
|
if (string_is_empty(core_filename))
|
|
return false;
|
|
|
|
memset(out_info, 0, sizeof(*out_info));
|
|
|
|
for (i = 0; i < core_info_list->count; i++)
|
|
{
|
|
const core_info_t *info = &core_info_list->list[i];
|
|
|
|
if (!info || (info->core_file_id.len == 0))
|
|
continue;
|
|
|
|
if (!strncmp(info->core_file_id.str, core_filename, info->core_file_id.len))
|
|
{
|
|
*out_info = *info;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#ifdef HAVE_COMPRESSION
|
|
static bool core_info_does_support_any_file(const core_info_t *core,
|
|
const struct string_list *list)
|
|
{
|
|
size_t i;
|
|
if (!list || !core || !core->supported_extensions_list)
|
|
return false;
|
|
|
|
for (i = 0; i < list->size; i++)
|
|
if (string_list_find_elem_prefix(core->supported_extensions_list,
|
|
".", path_get_extension(list->elems[i].data)))
|
|
return true;
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
static bool core_info_does_support_file(
|
|
const core_info_t *core, const char *path)
|
|
{
|
|
if (!core || !core->supported_extensions_list)
|
|
return false;
|
|
if (string_is_empty(path))
|
|
return false;
|
|
|
|
return string_list_find_elem_prefix(
|
|
core->supported_extensions_list, ".", path_get_extension(path));
|
|
}
|
|
|
|
/* qsort_r() is not in standard C, sadly. */
|
|
|
|
static int core_info_qsort_cmp(const void *a_, const void *b_)
|
|
{
|
|
core_info_state_t *p_coreinfo = coreinfo_get_ptr();
|
|
const core_info_t *a = (const core_info_t*)a_;
|
|
const core_info_t *b = (const core_info_t*)b_;
|
|
int support_a = core_info_does_support_file(a,
|
|
p_coreinfo->tmp_path);
|
|
int support_b = core_info_does_support_file(b,
|
|
p_coreinfo->tmp_path);
|
|
#ifdef HAVE_COMPRESSION
|
|
support_a = support_a ||
|
|
core_info_does_support_any_file(a, p_coreinfo->tmp_list);
|
|
support_b = support_b ||
|
|
core_info_does_support_any_file(b, p_coreinfo->tmp_list);
|
|
#endif
|
|
|
|
if (support_a != support_b)
|
|
return support_b - support_a;
|
|
return strcasecmp(a->display_name, b->display_name);
|
|
}
|
|
|
|
static core_info_t *core_info_find_internal(
|
|
core_info_list_t *list,
|
|
const char *core)
|
|
{
|
|
size_t i;
|
|
const char *core_filename = NULL;
|
|
|
|
if (!list || string_is_empty(core))
|
|
return NULL;
|
|
|
|
core_filename = path_basename(core);
|
|
if (string_is_empty(core_filename))
|
|
return NULL;
|
|
|
|
for (i = 0; i < list->count; i++)
|
|
{
|
|
core_info_t *info = core_info_get(list, i);
|
|
|
|
if (!info || (info->core_file_id.len == 0))
|
|
continue;
|
|
|
|
if (!strncmp(info->core_file_id.str, core_filename, info->core_file_id.len))
|
|
return info;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool core_info_list_update_missing_firmware_internal(
|
|
core_info_list_t *core_info_list,
|
|
const char *core,
|
|
const char *systemdir,
|
|
bool *set_missing_bios)
|
|
{
|
|
size_t i;
|
|
core_info_t *info = NULL;
|
|
char *path = NULL;
|
|
size_t path_size = PATH_MAX_LENGTH * sizeof(char);
|
|
|
|
if (!core_info_list || !core)
|
|
return false;
|
|
|
|
info = core_info_find_internal(core_info_list, core);
|
|
|
|
if (!info)
|
|
return false;
|
|
|
|
path = (char*)malloc(path_size);
|
|
|
|
if (!path)
|
|
return false;
|
|
|
|
path[0] = '\0';
|
|
|
|
for (i = 0; i < info->firmware_count; i++)
|
|
{
|
|
if (string_is_empty(info->firmware[i].path))
|
|
continue;
|
|
|
|
fill_pathname_join(path, systemdir,
|
|
info->firmware[i].path, path_size);
|
|
info->firmware[i].missing = !path_is_valid(path);
|
|
if (info->firmware[i].missing && !info->firmware[i].optional)
|
|
*set_missing_bios = true;
|
|
}
|
|
|
|
free(path);
|
|
return true;
|
|
}
|
|
|
|
void core_info_free_current_core(core_info_state_t *p_coreinfo)
|
|
{
|
|
if (p_coreinfo->current)
|
|
free(p_coreinfo->current);
|
|
p_coreinfo->current = NULL;
|
|
}
|
|
|
|
bool core_info_init_current_core(void)
|
|
{
|
|
core_info_state_t *p_coreinfo = coreinfo_get_ptr();
|
|
core_info_t *current = (core_info_t*)
|
|
malloc(sizeof(*current));
|
|
if (!current)
|
|
return false;
|
|
current->supports_no_game = false;
|
|
current->database_match_archive_member = false;
|
|
current->is_experimental = false;
|
|
current->is_locked = false;
|
|
current->firmware_count = 0;
|
|
current->path = NULL;
|
|
current->config_data = NULL;
|
|
current->display_name = NULL;
|
|
current->display_version = NULL;
|
|
current->core_name = NULL;
|
|
current->system_manufacturer = NULL;
|
|
current->systemname = NULL;
|
|
current->system_id = NULL;
|
|
current->supported_extensions = NULL;
|
|
current->authors = NULL;
|
|
current->permissions = NULL;
|
|
current->licenses = NULL;
|
|
current->categories = NULL;
|
|
current->databases = NULL;
|
|
current->notes = NULL;
|
|
current->required_hw_api = NULL;
|
|
current->description = NULL;
|
|
current->categories_list = NULL;
|
|
current->databases_list = NULL;
|
|
current->note_list = NULL;
|
|
current->supported_extensions_list = NULL;
|
|
current->authors_list = NULL;
|
|
current->permissions_list = NULL;
|
|
current->licenses_list = NULL;
|
|
current->required_hw_api_list = NULL;
|
|
current->firmware = NULL;
|
|
current->core_file_id.str = NULL;
|
|
current->core_file_id.len = 0;
|
|
current->userdata = NULL;
|
|
|
|
p_coreinfo->current = current;
|
|
return true;
|
|
}
|
|
|
|
bool core_info_get_current_core(core_info_t **core)
|
|
{
|
|
core_info_state_t *p_coreinfo = coreinfo_get_ptr();
|
|
if (!core)
|
|
return false;
|
|
*core = p_coreinfo->current;
|
|
return true;
|
|
}
|
|
|
|
void core_info_deinit_list(void)
|
|
{
|
|
core_info_state_t *p_coreinfo = coreinfo_get_ptr();
|
|
if (p_coreinfo->curr_list)
|
|
core_info_list_free(p_coreinfo->curr_list);
|
|
p_coreinfo->curr_list = NULL;
|
|
}
|
|
|
|
bool core_info_init_list(const char *path_info, const char *dir_cores,
|
|
const char *exts, bool dir_show_hidden_files)
|
|
{
|
|
core_info_state_t *p_coreinfo = coreinfo_get_ptr();
|
|
if (!(p_coreinfo->curr_list = core_info_list_new(dir_cores,
|
|
!string_is_empty(path_info) ? path_info : dir_cores,
|
|
exts,
|
|
dir_show_hidden_files)))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool core_info_get_list(core_info_list_t **core)
|
|
{
|
|
core_info_state_t *p_coreinfo = coreinfo_get_ptr();
|
|
if (!core)
|
|
return false;
|
|
*core = p_coreinfo->curr_list;
|
|
return true;
|
|
}
|
|
|
|
bool core_info_list_update_missing_firmware(core_info_ctx_firmware_t *info,
|
|
bool *set_missing_bios)
|
|
{
|
|
core_info_state_t *p_coreinfo = coreinfo_get_ptr();
|
|
if (!info)
|
|
return false;
|
|
return core_info_list_update_missing_firmware_internal(
|
|
p_coreinfo->curr_list,
|
|
info->path, info->directory.system,
|
|
set_missing_bios);
|
|
}
|
|
|
|
bool core_info_load(
|
|
core_info_ctx_find_t *info,
|
|
core_info_state_t *p_coreinfo)
|
|
{
|
|
core_info_t *core_info = NULL;
|
|
|
|
if (!info)
|
|
return false;
|
|
|
|
if (!p_coreinfo->current)
|
|
core_info_init_current_core();
|
|
|
|
core_info_get_current_core(&core_info);
|
|
|
|
if (!p_coreinfo->curr_list)
|
|
return false;
|
|
|
|
if (!core_info_list_get_info(p_coreinfo->curr_list,
|
|
core_info, info->path))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool core_info_find(core_info_ctx_find_t *info)
|
|
{
|
|
core_info_state_t *p_coreinfo = coreinfo_get_ptr();
|
|
|
|
if (!info || !p_coreinfo->curr_list)
|
|
return false;
|
|
|
|
info->inf = core_info_find_internal(p_coreinfo->curr_list, info->path);
|
|
|
|
if (!info->inf)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
core_info_t *core_info_get(core_info_list_t *list, size_t i)
|
|
{
|
|
core_info_t *info = NULL;
|
|
|
|
if (!list)
|
|
return NULL;
|
|
info = (core_info_t*)&list->list[i];
|
|
if (!info || !info->path)
|
|
return NULL;
|
|
|
|
return info;
|
|
}
|
|
|
|
void core_info_list_get_supported_cores(core_info_list_t *core_info_list,
|
|
const char *path, const core_info_t **infos, size_t *num_infos)
|
|
{
|
|
size_t i;
|
|
size_t supported = 0;
|
|
#ifdef HAVE_COMPRESSION
|
|
struct string_list *list = NULL;
|
|
#endif
|
|
core_info_state_t *p_coreinfo = coreinfo_get_ptr();
|
|
|
|
if (!core_info_list)
|
|
return;
|
|
|
|
p_coreinfo->tmp_path = path;
|
|
|
|
#ifdef HAVE_COMPRESSION
|
|
if (path_is_compressed_file(path))
|
|
list = file_archive_get_file_list(path, NULL);
|
|
p_coreinfo->tmp_list = list;
|
|
#endif
|
|
|
|
/* Let supported core come first in list so we can return
|
|
* a pointer to them. */
|
|
qsort(core_info_list->list, core_info_list->count,
|
|
sizeof(core_info_t), core_info_qsort_cmp);
|
|
|
|
for (i = 0; i < core_info_list->count; i++, supported++)
|
|
{
|
|
const core_info_t *core = &core_info_list->list[i];
|
|
|
|
if (core_info_does_support_file(core, path))
|
|
continue;
|
|
|
|
#ifdef HAVE_COMPRESSION
|
|
if (core_info_does_support_any_file(core, list))
|
|
continue;
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
|
|
#ifdef HAVE_COMPRESSION
|
|
if (list)
|
|
string_list_free(list);
|
|
#endif
|
|
|
|
*infos = core_info_list->list;
|
|
*num_infos = supported;
|
|
}
|
|
|
|
void core_info_get_name(const char *path, char *s, size_t len,
|
|
const char *path_info, const char *dir_cores,
|
|
const char *exts, bool dir_show_hidden_files,
|
|
bool get_display_name)
|
|
{
|
|
size_t i;
|
|
const char *path_basedir = !string_is_empty(path_info) ?
|
|
path_info : dir_cores;
|
|
struct string_list *contents = dir_list_new(
|
|
dir_cores, exts, false, dir_show_hidden_files, false, false);
|
|
const char *core_path_basename = path_basename(path);
|
|
|
|
if (!contents)
|
|
return;
|
|
|
|
for (i = 0; i < contents->size; i++)
|
|
{
|
|
struct config_entry_list
|
|
*entry = NULL;
|
|
config_file_t *conf = NULL;
|
|
const char *current_path = contents->elems[i].data;
|
|
|
|
if (!string_is_equal(path_basename(current_path), core_path_basename))
|
|
continue;
|
|
|
|
conf = core_info_list_iterate(contents->elems[i].data,
|
|
path_basedir);
|
|
|
|
if (!conf)
|
|
continue;
|
|
|
|
if (get_display_name)
|
|
entry = config_get_entry(conf, "display_name", NULL);
|
|
else
|
|
entry = config_get_entry(conf, "corename", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
strlcpy(s, entry->value, len);
|
|
|
|
config_file_free(conf);
|
|
break;
|
|
}
|
|
|
|
dir_list_free(contents);
|
|
contents = NULL;
|
|
}
|
|
|
|
size_t core_info_list_num_info_files(core_info_list_t *core_info_list)
|
|
{
|
|
size_t i, num = 0;
|
|
|
|
if (!core_info_list)
|
|
return 0;
|
|
|
|
for (i = 0; i < core_info_list->count; i++)
|
|
{
|
|
config_file_t *conf = (config_file_t*)
|
|
core_info_list->list[i].config_data;
|
|
num += !!conf;
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
bool core_info_database_match_archive_member(const char *database_path)
|
|
{
|
|
char *database = NULL;
|
|
const char *new_path = path_basename(database_path);
|
|
core_info_state_t *p_coreinfo = coreinfo_get_ptr();
|
|
|
|
if (string_is_empty(new_path))
|
|
return false;
|
|
|
|
database = strdup(new_path);
|
|
|
|
if (string_is_empty(database))
|
|
goto error;
|
|
|
|
path_remove_extension(database);
|
|
|
|
if (p_coreinfo->curr_list)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < p_coreinfo->curr_list->count; i++)
|
|
{
|
|
const core_info_t *info = &p_coreinfo->curr_list->list[i];
|
|
|
|
if (!info->database_match_archive_member)
|
|
continue;
|
|
|
|
if (!string_list_find_elem(info->databases_list, database))
|
|
continue;
|
|
|
|
free(database);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
error:
|
|
if (database)
|
|
free(database);
|
|
return false;
|
|
}
|
|
|
|
bool core_info_database_supports_content_path(
|
|
const char *database_path, const char *path)
|
|
{
|
|
char *database = NULL;
|
|
const char *new_path = path_basename(database_path);
|
|
core_info_state_t *p_coreinfo = coreinfo_get_ptr();
|
|
|
|
if (string_is_empty(new_path))
|
|
return false;
|
|
|
|
database = strdup(new_path);
|
|
|
|
if (string_is_empty(database))
|
|
goto error;
|
|
|
|
path_remove_extension(database);
|
|
|
|
if (p_coreinfo->curr_list)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < p_coreinfo->curr_list->count; i++)
|
|
{
|
|
const core_info_t *info = &p_coreinfo->curr_list->list[i];
|
|
|
|
if (!string_list_find_elem(info->supported_extensions_list,
|
|
path_get_extension(path)))
|
|
continue;
|
|
|
|
if (!string_list_find_elem(info->databases_list, database))
|
|
continue;
|
|
|
|
free(database);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
error:
|
|
if (database)
|
|
free(database);
|
|
return false;
|
|
}
|
|
|
|
bool core_info_list_get_display_name(core_info_list_t *core_info_list,
|
|
const char *path, char *s, size_t len)
|
|
{
|
|
size_t i;
|
|
const char *core_filename = NULL;
|
|
|
|
if (!core_info_list || string_is_empty(path))
|
|
return false;
|
|
|
|
core_filename = path_basename(path);
|
|
if (string_is_empty(core_filename))
|
|
return false;
|
|
|
|
for (i = 0; i < core_info_list->count; i++)
|
|
{
|
|
const core_info_t *info = &core_info_list->list[i];
|
|
|
|
if (!info || (info->core_file_id.len == 0))
|
|
continue;
|
|
|
|
if (!strncmp(info->core_file_id.str, core_filename, info->core_file_id.len))
|
|
{
|
|
if (string_is_empty(info->display_name))
|
|
break;
|
|
|
|
strlcpy(s, info->display_name, len);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool core_info_get_display_name(const char *path, char *s, size_t len)
|
|
{
|
|
struct config_entry_list
|
|
*entry = NULL;
|
|
config_file_t *conf = config_file_new_from_path_to_string(path);
|
|
|
|
if (!conf)
|
|
return false;
|
|
|
|
entry = config_get_entry(conf, "display_name", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
strlcpy(s, entry->value, len);
|
|
|
|
config_file_free(conf);
|
|
return true;
|
|
}
|
|
|
|
/* Returns core_info parameters required for
|
|
* core updater tasks, read from specified file.
|
|
* Returned core_updater_info_t object must be
|
|
* freed using core_info_free_core_updater_info().
|
|
* Returns NULL if 'path' is invalid. */
|
|
core_updater_info_t *core_info_get_core_updater_info(const char *path)
|
|
{
|
|
struct config_entry_list
|
|
*entry = NULL;
|
|
bool tmp_bool = false;
|
|
core_updater_info_t *info = NULL;
|
|
config_file_t *conf = NULL;
|
|
|
|
if (string_is_empty(path))
|
|
return NULL;
|
|
|
|
/* Read config file */
|
|
conf = config_file_new_from_path_to_string(path);
|
|
|
|
if (!conf)
|
|
return NULL;
|
|
|
|
/* Create info struct */
|
|
info = (core_updater_info_t*)malloc(sizeof(*info));
|
|
|
|
if (!info)
|
|
return NULL;
|
|
|
|
info->is_experimental = false;
|
|
info->display_name = NULL;
|
|
info->description = NULL;
|
|
info->licenses = NULL;
|
|
|
|
/* Fetch required parameters */
|
|
|
|
/* > is_experimental */
|
|
if (config_get_bool(conf, "is_experimental", &tmp_bool))
|
|
info->is_experimental = tmp_bool;
|
|
|
|
/* > display_name */
|
|
entry = config_get_entry(conf, "display_name", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
info->display_name = strdup(entry->value);
|
|
|
|
/* > description */
|
|
entry = config_get_entry(conf, "description", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
info->description = strdup(entry->value);
|
|
|
|
/* > licenses */
|
|
entry = config_get_entry(conf, "license", NULL);
|
|
|
|
if (entry && !string_is_empty(entry->value))
|
|
info->licenses = strdup(entry->value);
|
|
|
|
/* Clean up */
|
|
config_file_free(conf);
|
|
|
|
return info;
|
|
}
|
|
|
|
void core_info_free_core_updater_info(core_updater_info_t *info)
|
|
{
|
|
if (!info)
|
|
return;
|
|
|
|
if (info->display_name)
|
|
free(info->display_name);
|
|
|
|
if (info->description)
|
|
free(info->description);
|
|
|
|
if (info->licenses)
|
|
free(info->licenses);
|
|
|
|
free(info);
|
|
info = NULL;
|
|
}
|
|
|
|
static int core_info_qsort_func_path(const core_info_t *a,
|
|
const core_info_t *b)
|
|
{
|
|
if (!a || !b)
|
|
return 0;
|
|
|
|
if (string_is_empty(a->path) || string_is_empty(b->path))
|
|
return 0;
|
|
|
|
return strcasecmp(a->path, b->path);
|
|
}
|
|
|
|
static int core_info_qsort_func_display_name(const core_info_t *a,
|
|
const core_info_t *b)
|
|
{
|
|
if (!a || !b)
|
|
return 0;
|
|
|
|
if (string_is_empty(a->display_name) || string_is_empty(b->display_name))
|
|
return 0;
|
|
|
|
return strcasecmp(a->display_name, b->display_name);
|
|
}
|
|
|
|
static int core_info_qsort_func_core_name(const core_info_t *a,
|
|
const core_info_t *b)
|
|
{
|
|
if (!a || !b)
|
|
return 0;
|
|
|
|
if (string_is_empty(a->core_name) || string_is_empty(b->core_name))
|
|
return 0;
|
|
|
|
return strcasecmp(a->core_name, b->core_name);
|
|
}
|
|
|
|
static int core_info_qsort_func_system_name(const core_info_t *a,
|
|
const core_info_t *b)
|
|
{
|
|
if (!a || !b)
|
|
return 0;
|
|
|
|
if (string_is_empty(a->systemname) || string_is_empty(b->systemname))
|
|
return 0;
|
|
|
|
return strcasecmp(a->systemname, b->systemname);
|
|
}
|
|
|
|
void core_info_qsort(core_info_list_t *core_info_list,
|
|
enum core_info_list_qsort_type qsort_type)
|
|
{
|
|
if (!core_info_list)
|
|
return;
|
|
|
|
if (core_info_list->count < 2)
|
|
return;
|
|
|
|
switch (qsort_type)
|
|
{
|
|
case CORE_INFO_LIST_SORT_PATH:
|
|
qsort(core_info_list->list,
|
|
core_info_list->count,
|
|
sizeof(core_info_t),
|
|
(int (*)(const void *, const void *))
|
|
core_info_qsort_func_path);
|
|
break;
|
|
case CORE_INFO_LIST_SORT_DISPLAY_NAME:
|
|
qsort(core_info_list->list,
|
|
core_info_list->count,
|
|
sizeof(core_info_t),
|
|
(int (*)(const void *, const void *))
|
|
core_info_qsort_func_display_name);
|
|
break;
|
|
case CORE_INFO_LIST_SORT_CORE_NAME:
|
|
qsort(core_info_list->list,
|
|
core_info_list->count,
|
|
sizeof(core_info_t),
|
|
(int (*)(const void *, const void *))
|
|
core_info_qsort_func_core_name);
|
|
break;
|
|
case CORE_INFO_LIST_SORT_SYSTEM_NAME:
|
|
qsort(core_info_list->list,
|
|
core_info_list->count,
|
|
sizeof(core_info_t),
|
|
(int (*)(const void *, const void *))
|
|
core_info_qsort_func_system_name);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
static bool core_info_compare_api_version(int sys_major, int sys_minor, int major, int minor, enum compare_op op)
|
|
{
|
|
switch (op)
|
|
{
|
|
case COMPARE_OP_EQUAL:
|
|
if (sys_major == major && sys_minor == minor)
|
|
return true;
|
|
break;
|
|
case COMPARE_OP_NOT_EQUAL:
|
|
if (!(sys_major == major && sys_minor == minor))
|
|
return true;
|
|
break;
|
|
case COMPARE_OP_LESS:
|
|
if (sys_major < major || (sys_major == major && sys_minor < minor))
|
|
return true;
|
|
break;
|
|
case COMPARE_OP_LESS_EQUAL:
|
|
if (sys_major < major || (sys_major == major && sys_minor <= minor))
|
|
return true;
|
|
break;
|
|
case COMPARE_OP_GREATER:
|
|
if (sys_major > major || (sys_major == major && sys_minor > minor))
|
|
return true;
|
|
break;
|
|
case COMPARE_OP_GREATER_EQUAL:
|
|
if (sys_major > major || (sys_major == major && sys_minor >= minor))
|
|
return true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool core_info_hw_api_supported(core_info_t *info)
|
|
{
|
|
#ifdef RARCH_INTERNAL
|
|
unsigned i;
|
|
enum gfx_ctx_api sys_api;
|
|
gfx_ctx_flags_t sys_flags = {0};
|
|
const char *sys_api_version_str = video_driver_get_gpu_api_version_string();
|
|
int sys_api_version_major = 0;
|
|
int sys_api_version_minor = 0;
|
|
|
|
enum api_parse_state
|
|
{
|
|
STATE_API_NAME,
|
|
STATE_API_COMPARE_OP,
|
|
STATE_API_VERSION
|
|
};
|
|
|
|
if (!info || !info->required_hw_api_list || info->required_hw_api_list->size == 0)
|
|
return true;
|
|
|
|
sys_api = video_context_driver_get_api();
|
|
video_context_driver_get_flags(&sys_flags);
|
|
|
|
for (i = 0; i < info->required_hw_api_list->size; i++)
|
|
{
|
|
char api_str[32] = {0};
|
|
char version[16] = {0};
|
|
char major_str[16] = {0};
|
|
char minor_str[16] = {0};
|
|
const char *cur_api = info->required_hw_api_list->elems[i].data;
|
|
int api_pos = 0;
|
|
int major_str_pos = 0;
|
|
int minor_str_pos = 0;
|
|
int cur_api_len = 0;
|
|
int j = 0;
|
|
int major = 0;
|
|
int minor = 0;
|
|
bool found_major = false;
|
|
bool found_minor = false;
|
|
enum compare_op op = COMPARE_OP_GREATER_EQUAL;
|
|
enum api_parse_state state = STATE_API_NAME;
|
|
|
|
if (string_is_empty(cur_api))
|
|
continue;
|
|
|
|
cur_api_len = (int)strlen(cur_api);
|
|
|
|
for (j = 0; j < cur_api_len; j++)
|
|
{
|
|
if (cur_api[j] == ' ')
|
|
continue;
|
|
|
|
switch (state)
|
|
{
|
|
case STATE_API_NAME:
|
|
{
|
|
if (isupper(cur_api[j]) || islower(cur_api[j]))
|
|
api_str[api_pos++] = cur_api[j];
|
|
else
|
|
{
|
|
j--;
|
|
state = STATE_API_COMPARE_OP;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case STATE_API_COMPARE_OP:
|
|
{
|
|
if (j < cur_api_len - 1 && !(cur_api[j] >= '0' && cur_api[j] <= '9'))
|
|
{
|
|
if (cur_api[j] == '=' && cur_api[j + 1] == '=')
|
|
{
|
|
op = COMPARE_OP_EQUAL;
|
|
j++;
|
|
}
|
|
else if (cur_api[j] == '=')
|
|
op = COMPARE_OP_EQUAL;
|
|
else if (cur_api[j] == '!' && cur_api[j + 1] == '=')
|
|
{
|
|
op = COMPARE_OP_NOT_EQUAL;
|
|
j++;
|
|
}
|
|
else if (cur_api[j] == '<' && cur_api[j + 1] == '=')
|
|
{
|
|
op = COMPARE_OP_LESS_EQUAL;
|
|
j++;
|
|
}
|
|
else if (cur_api[j] == '>' && cur_api[j + 1] == '=')
|
|
{
|
|
op = COMPARE_OP_GREATER_EQUAL;
|
|
j++;
|
|
}
|
|
else if (cur_api[j] == '<')
|
|
op = COMPARE_OP_LESS;
|
|
else if (cur_api[j] == '>')
|
|
op = COMPARE_OP_GREATER;
|
|
}
|
|
|
|
state = STATE_API_VERSION;
|
|
|
|
break;
|
|
}
|
|
case STATE_API_VERSION:
|
|
{
|
|
if (!found_minor && cur_api[j] >= '0' && cur_api[j] <= '9' && cur_api[j] != '.')
|
|
{
|
|
found_major = true;
|
|
|
|
if (major_str_pos < sizeof(major_str) - 1)
|
|
major_str[major_str_pos++] = cur_api[j];
|
|
}
|
|
else if (found_major && found_minor && cur_api[j] >= '0' && cur_api[j] <= '9')
|
|
{
|
|
if (minor_str_pos < sizeof(minor_str) - 1)
|
|
minor_str[minor_str_pos++] = cur_api[j];
|
|
}
|
|
else if (cur_api[j] == '.')
|
|
found_minor = true;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
sscanf(major_str, "%d", &major);
|
|
sscanf(minor_str, "%d", &minor);
|
|
snprintf(version, sizeof(version), "%d.%d", major, minor);
|
|
#if 0
|
|
printf("Major: %d\n", major);
|
|
printf("Minor: %d\n", minor);
|
|
printf("API: %s\n", api_str);
|
|
printf("Version: %s\n", version);
|
|
fflush(stdout);
|
|
#endif
|
|
|
|
if ((string_is_equal_noncase(api_str, "opengl") && sys_api == GFX_CTX_OPENGL_API) ||
|
|
(string_is_equal_noncase(api_str, "openglcompat") && sys_api == GFX_CTX_OPENGL_API) ||
|
|
(string_is_equal_noncase(api_str, "openglcompatibility") && sys_api == GFX_CTX_OPENGL_API))
|
|
{
|
|
/* system is running a core context while compat is requested */
|
|
if (sys_flags.flags & (1 << GFX_CTX_FLAGS_GL_CORE_CONTEXT))
|
|
return false;
|
|
|
|
sscanf(sys_api_version_str, "%d.%d", &sys_api_version_major, &sys_api_version_minor);
|
|
|
|
if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
|
|
return true;
|
|
}
|
|
else if (string_is_equal_noncase(api_str, "openglcore") && sys_api == GFX_CTX_OPENGL_API)
|
|
{
|
|
sscanf(sys_api_version_str, "%d.%d", &sys_api_version_major, &sys_api_version_minor);
|
|
|
|
if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
|
|
return true;
|
|
}
|
|
else if (string_is_equal_noncase(api_str, "opengles") && sys_api == GFX_CTX_OPENGL_ES_API)
|
|
{
|
|
sscanf(sys_api_version_str, "OpenGL ES %d.%d", &sys_api_version_major, &sys_api_version_minor);
|
|
|
|
if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
|
|
return true;
|
|
}
|
|
else if (string_is_equal_noncase(api_str, "direct3d8") && sys_api == GFX_CTX_DIRECT3D8_API)
|
|
{
|
|
sys_api_version_major = 8;
|
|
sys_api_version_minor = 0;
|
|
|
|
if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
|
|
return true;
|
|
}
|
|
else if (string_is_equal_noncase(api_str, "direct3d9") && sys_api == GFX_CTX_DIRECT3D9_API)
|
|
{
|
|
sys_api_version_major = 9;
|
|
sys_api_version_minor = 0;
|
|
|
|
if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
|
|
return true;
|
|
}
|
|
else if (string_is_equal_noncase(api_str, "direct3d10") && sys_api == GFX_CTX_DIRECT3D10_API)
|
|
{
|
|
sys_api_version_major = 10;
|
|
sys_api_version_minor = 0;
|
|
|
|
if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
|
|
return true;
|
|
}
|
|
else if (string_is_equal_noncase(api_str, "direct3d11") && sys_api == GFX_CTX_DIRECT3D11_API)
|
|
{
|
|
sys_api_version_major = 11;
|
|
sys_api_version_minor = 0;
|
|
|
|
if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
|
|
return true;
|
|
}
|
|
else if (string_is_equal_noncase(api_str, "direct3d12") && sys_api == GFX_CTX_DIRECT3D12_API)
|
|
{
|
|
sys_api_version_major = 12;
|
|
sys_api_version_minor = 0;
|
|
|
|
if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
|
|
return true;
|
|
}
|
|
else if (string_is_equal_noncase(api_str, "vulkan") && sys_api == GFX_CTX_VULKAN_API)
|
|
{
|
|
sscanf(sys_api_version_str, "%d.%d", &sys_api_version_major, &sys_api_version_minor);
|
|
|
|
if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
|
|
return true;
|
|
}
|
|
else if (string_is_equal_noncase(api_str, "metal") && sys_api == GFX_CTX_METAL_API)
|
|
{
|
|
sscanf(sys_api_version_str, "%d.%d", &sys_api_version_major, &sys_api_version_minor);
|
|
|
|
if (core_info_compare_api_version(sys_api_version_major, sys_api_version_minor, major, minor, op))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
/* Sets 'locked' status of specified core
|
|
* > Returns true if successful
|
|
* > Like all functions that access the cached
|
|
* core info list this is *not* thread safe */
|
|
bool core_info_set_core_lock(const char *core_path, bool lock)
|
|
{
|
|
char *lock_file_path = NULL;
|
|
RFILE *lock_file = NULL;
|
|
bool lock_file_exists = false;
|
|
core_info_ctx_find_t core_info;
|
|
|
|
if (string_is_empty(core_path))
|
|
goto error;
|
|
|
|
/* Search for specified core */
|
|
core_info.inf = NULL;
|
|
core_info.path = core_path;
|
|
|
|
if (!core_info_find(&core_info))
|
|
goto error;
|
|
|
|
/* Get associated lock file path */
|
|
lock_file_path = core_info_get_core_lock_file_path(core_info.inf->path);
|
|
|
|
if (string_is_empty(lock_file_path))
|
|
goto error;
|
|
|
|
/* Check whether lock file exists */
|
|
lock_file_exists = path_is_valid(lock_file_path);
|
|
|
|
/* Create or delete lock file, as required */
|
|
if (lock && !lock_file_exists)
|
|
{
|
|
lock_file = filestream_open(
|
|
lock_file_path,
|
|
RETRO_VFS_FILE_ACCESS_WRITE,
|
|
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
|
|
|
if (!lock_file)
|
|
goto error;
|
|
|
|
/* We have to write something - just output
|
|
* a single character */
|
|
if (filestream_putc(lock_file, 0) != 0)
|
|
goto error;
|
|
|
|
filestream_close(lock_file);
|
|
lock_file = NULL;
|
|
}
|
|
else if (!lock && lock_file_exists)
|
|
if (filestream_delete(lock_file_path) != 0)
|
|
goto error;
|
|
|
|
/* Clean up */
|
|
free(lock_file_path);
|
|
lock_file_path = NULL;
|
|
|
|
/* File operations were successful - update
|
|
* core info entry */
|
|
core_info.inf->is_locked = lock;
|
|
|
|
return true;
|
|
|
|
error:
|
|
if (lock_file_path)
|
|
{
|
|
free(lock_file_path);
|
|
lock_file_path = NULL;
|
|
}
|
|
|
|
if (lock_file)
|
|
{
|
|
filestream_close(lock_file);
|
|
lock_file = NULL;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Fetches 'locked' status of specified core
|
|
* > If 'validate_path' is 'true', will search
|
|
* cached core info list for a corresponding
|
|
* 'sanitised' core file path. This is *not*
|
|
* thread safe
|
|
* > If 'validate_path' is 'false', performs a
|
|
* direct filesystem check. This *is* thread
|
|
* safe, but validity of specified core path
|
|
* must be checked externally */
|
|
bool core_info_get_core_lock(const char *core_path, bool validate_path)
|
|
{
|
|
const char *core_file_path = NULL;
|
|
char *lock_file_path = NULL;
|
|
bool is_locked = false;
|
|
core_info_ctx_find_t core_info;
|
|
|
|
if (string_is_empty(core_path))
|
|
goto end;
|
|
|
|
/* Check whether core path is to be validated */
|
|
if (validate_path)
|
|
{
|
|
core_info.inf = NULL;
|
|
core_info.path = core_path;
|
|
|
|
if (core_info_find(&core_info))
|
|
core_file_path = core_info.inf->path;
|
|
}
|
|
else
|
|
core_file_path = core_path;
|
|
|
|
/* A core cannot be locked if it does not exist... */
|
|
if (string_is_empty(core_file_path) ||
|
|
!path_is_valid(core_file_path))
|
|
goto end;
|
|
|
|
/* Get lock file path */
|
|
lock_file_path = core_info_get_core_lock_file_path(core_file_path);
|
|
|
|
if (string_is_empty(lock_file_path))
|
|
goto end;
|
|
|
|
/* Check whether lock file exists */
|
|
is_locked = path_is_valid(lock_file_path);
|
|
|
|
/* If core path has been validated (and a
|
|
* core info object is available), ensure
|
|
* that core info 'is_locked' field is
|
|
* up to date */
|
|
if (validate_path && core_info.inf)
|
|
core_info.inf->is_locked = is_locked;
|
|
|
|
end:
|
|
if (lock_file_path)
|
|
{
|
|
free(lock_file_path);
|
|
lock_file_path = NULL;
|
|
}
|
|
|
|
return is_locked;
|
|
}
|