(Android/Play Store) Add frontend support for core installation via Play Feature Delivery

This commit is contained in:
jdgleaver 2020-08-28 16:27:48 +01:00
parent 2c07234a5d
commit 4799d2d955
19 changed files with 1504 additions and 115 deletions

View File

@ -2212,6 +2212,15 @@ ifeq ($(HAVE_VITAGL), 1)
OBJ += $(patsubst %.c,%.o,$(foreach dir,$(SOURCES), $(wildcard $(dir)/*.c))) OBJ += $(patsubst %.c,%.o,$(foreach dir,$(SOURCES), $(wildcard $(dir)/*.c)))
endif endif
#####################################
### Android Play Feature Delivery ###
### (Play Store build core ###
### downloader) ###
###############WIP###################
ifeq ($(ANDROID), 1)
OBJ += play_feature_delivery/play_feature_delivery.o
endif
################################## ##################################
### Classic Platform specifics ### ### Classic Platform specifics ###
###############WIP################ ###############WIP################

View File

@ -36,6 +36,10 @@
#include "uwp/uwp_func.h" #include "uwp/uwp_func.h"
#endif #endif
#if defined(ANDROID)
#include "play_feature_delivery/play_feature_delivery.h"
#endif
enum compare_op enum compare_op
{ {
COMPARE_OP_EQUAL = 0, COMPARE_OP_EQUAL = 0,
@ -1524,6 +1528,13 @@ bool core_info_set_core_lock(const char *core_path, bool lock)
lock_file_path[0] = '\0'; lock_file_path[0] = '\0';
#if defined(ANDROID)
/* Play Store builds do not support
* core locking */
if (play_feature_delivery_enabled())
return false;
#endif
if (string_is_empty(core_path)) if (string_is_empty(core_path))
return false; return false;
@ -1599,6 +1610,13 @@ bool core_info_get_core_lock(const char *core_path, bool validate_path)
lock_file_path[0] = '\0'; lock_file_path[0] = '\0';
#if defined(ANDROID)
/* Play Store builds do not support
* core locking */
if (play_feature_delivery_enabled())
return false;
#endif
if (string_is_empty(core_path)) if (string_is_empty(core_path))
return false; return false;

View File

@ -36,6 +36,7 @@
struct core_updater_list struct core_updater_list
{ {
core_updater_list_entry_t *entries; core_updater_list_entry_t *entries;
enum core_updater_list_type type;
}; };
/* Cached ('global') core updater list */ /* Cached ('global') core updater list */
@ -109,6 +110,7 @@ core_updater_list_t *core_updater_list_init(void)
/* Initialise members */ /* Initialise members */
core_list->entries = NULL; core_list->entries = NULL;
core_list->type = CORE_UPDATER_LIST_TYPE_UNKNOWN;
return core_list; return core_list;
} }
@ -129,6 +131,8 @@ void core_updater_list_reset(core_updater_list_t *core_list)
RBUF_FREE(core_list->entries); RBUF_FREE(core_list->entries);
} }
core_list->type = CORE_UPDATER_LIST_TYPE_UNKNOWN;
} }
/* Frees specified core updater list */ /* Frees specified core updater list */
@ -194,6 +198,17 @@ size_t core_updater_list_size(core_updater_list_t *core_list)
return RBUF_LEN(core_list->entries); return RBUF_LEN(core_list->entries);
} }
/* Returns 'type' (core delivery method) of
* specified core updater list */
enum core_updater_list_type core_updater_list_get_type(
core_updater_list_t *core_list)
{
if (!core_list)
return CORE_UPDATER_LIST_TYPE_UNKNOWN;
return core_list->type;
}
/* Fetches core updater list entry corresponding /* Fetches core updater list entry corresponding
* to the specified entry index. * to the specified entry index.
* Returns false if index is invalid. */ * Returns false if index is invalid. */
@ -371,7 +386,8 @@ static bool core_updater_list_set_paths(
const char *path_dir_libretro, const char *path_dir_libretro,
const char *path_libretro_info, const char *path_libretro_info,
const char *network_buildbot_url, const char *network_buildbot_url,
const char *filename_str) const char *filename_str,
enum core_updater_list_type list_type)
{ {
char *last_underscore = NULL; char *last_underscore = NULL;
char *tmp_url = NULL; char *tmp_url = NULL;
@ -384,11 +400,14 @@ static bool core_updater_list_set_paths(
local_core_path[0] = '\0'; local_core_path[0] = '\0';
local_info_path[0] = '\0'; local_info_path[0] = '\0';
if (!entry || string_is_empty(filename_str)) if (!entry ||
string_is_empty(filename_str) ||
string_is_empty(path_dir_libretro) ||
string_is_empty(path_libretro_info))
return false; return false;
if (string_is_empty(path_dir_libretro) || /* Only buildbot cores require the buildbot URL */
string_is_empty(path_libretro_info) || if ((list_type == CORE_UPDATER_LIST_TYPE_BUILDBOT) &&
string_is_empty(network_buildbot_url)) string_is_empty(network_buildbot_url))
return false; return false;
@ -404,20 +423,24 @@ static bool core_updater_list_set_paths(
entry->remote_filename = strdup(filename_str); entry->remote_filename = strdup(filename_str);
/* remote_core_path */ /* remote_core_path
fill_pathname_join( * > Leave blank if this is not a buildbot core */
remote_core_path, if (list_type == CORE_UPDATER_LIST_TYPE_BUILDBOT)
network_buildbot_url, {
filename_str, fill_pathname_join(
sizeof(remote_core_path)); remote_core_path,
network_buildbot_url,
filename_str,
sizeof(remote_core_path));
/* > Apply proper URL encoding (messy...) */ /* > Apply proper URL encoding (messy...) */
tmp_url = strdup(remote_core_path); tmp_url = strdup(remote_core_path);
remote_core_path[0] = '\0'; remote_core_path[0] = '\0';
net_http_urlencode_full( net_http_urlencode_full(
remote_core_path, tmp_url, sizeof(remote_core_path)); remote_core_path, tmp_url, sizeof(remote_core_path));
if (tmp_url) if (tmp_url)
free(tmp_url); free(tmp_url);
}
if (entry->remote_core_path) if (entry->remote_core_path)
{ {
@ -677,7 +700,8 @@ static void core_updater_list_add_entry(
path_dir_libretro, path_dir_libretro,
path_libretro_info, path_libretro_info,
network_buildbot_url, network_buildbot_url,
filename_str)) filename_str,
CORE_UPDATER_LIST_TYPE_BUILDBOT))
goto error; goto error;
if (!core_updater_list_set_core_info( if (!core_updater_list_set_core_info(
@ -756,8 +780,8 @@ bool core_updater_list_parse_network_data(
const char *data, size_t len) const char *data, size_t len)
{ {
size_t i; size_t i;
char *data_buf = NULL; char *data_buf = NULL;
struct string_list network_core_list = {0}; struct string_list network_core_list = {0};
/* Sanity check */ /* Sanity check */
if (!core_list || string_is_empty(data) || (len < 1)) if (!core_list || string_is_empty(data) || (len < 1))
@ -825,6 +849,9 @@ bool core_updater_list_parse_network_data(
/* Sort completed list */ /* Sort completed list */
core_updater_list_qsort(core_list); core_updater_list_qsort(core_list);
/* Set list type */
core_list->type = CORE_UPDATER_LIST_TYPE_BUILDBOT;
return true; return true;
error: error:
@ -835,3 +862,114 @@ error:
return false; return false;
} }
/* Parses a single play feature delivery core
* listing and adds it to the specified core
* updater list */
static void core_updater_list_add_pfd_entry(
core_updater_list_t *core_list,
const char *path_dir_libretro,
const char *path_libretro_info,
const char *filename_str)
{
const core_updater_list_entry_t *search_entry = NULL;
core_updater_list_entry_t entry = {0};
if (!core_list || string_is_empty(filename_str))
goto error;
/* Check whether core file is already included
* in the list (this is *not* an error condition,
* it just means we can skip the current listing) */
if (core_updater_list_get_filename(core_list,
filename_str, &search_entry))
goto error;
/* Note: Play feature delivery cores have no
* timestamp or CRC info - leave these fields
* zero initialised */
/* Populate entry fields */
if (!core_updater_list_set_paths(
&entry,
path_dir_libretro,
path_libretro_info,
NULL,
filename_str,
CORE_UPDATER_LIST_TYPE_PFD))
goto error;
if (!core_updater_list_set_core_info(
&entry,
entry.local_info_path,
filename_str))
goto error;
/* Add entry to list */
if (!core_updater_list_push_entry(core_list, &entry))
goto error;
return;
error:
/* This is not a *fatal* error - it just
* means one of the following:
* - The core listing entry obtained from the
* play feature delivery interface is broken
* somehow
* - We had insufficient memory to allocate a new
* entry in the core updater list
* In either case, the current entry is discarded
* and we move on to the next one */
core_updater_list_free_entry(&entry);
}
/* Reads the list of cores currently available
* via play feature delivery (PFD) into the
* specified core_updater_list_t object.
* Returns false in the event of an error. */
bool core_updater_list_parse_pfd_data(
core_updater_list_t *core_list,
const char *path_dir_libretro,
const char *path_libretro_info,
const struct string_list *pfd_cores)
{
size_t i;
/* Sanity check */
if (!core_list || !pfd_cores || (pfd_cores->size < 1))
return false;
/* We're populating a list 'from scratch' - remove
* any existing entries */
core_updater_list_reset(core_list);
/* Loop over play feature delivery core list */
for (i = 0; i < pfd_cores->size; i++)
{
const char *filename_str = pfd_cores->elems[i].data;
if (string_is_empty(filename_str))
continue;
/* Parse core file name and add to core
* updater list */
core_updater_list_add_pfd_entry(
core_list,
path_dir_libretro,
path_libretro_info,
filename_str);
}
/* Sanity check */
if (RBUF_LEN(core_list->entries) < 1)
return false;
/* Sort completed list */
core_updater_list_qsort(core_list);
/* Set list type */
core_list->type = CORE_UPDATER_LIST_TYPE_PFD;
return true;
}

View File

@ -32,6 +32,18 @@
RETRO_BEGIN_DECLS RETRO_BEGIN_DECLS
/* Defines all possible 'types' of core
* updater list - corresponds to core
* delivery method:
* > Buildbot
* > Play feature delivery (PFD) */
enum core_updater_list_type
{
CORE_UPDATER_LIST_TYPE_UNKNOWN = 0,
CORE_UPDATER_LIST_TYPE_BUILDBOT,
CORE_UPDATER_LIST_TYPE_PFD
};
/* Holds all date info for a core file /* Holds all date info for a core file
* on the buildbot */ * on the buildbot */
typedef struct typedef struct
@ -99,6 +111,11 @@ void core_updater_list_free_cached(void);
/* Returns number of entries in core updater list */ /* Returns number of entries in core updater list */
size_t core_updater_list_size(core_updater_list_t *core_list); size_t core_updater_list_size(core_updater_list_t *core_list);
/* Returns 'type' (core delivery method) of
* specified core updater list */
enum core_updater_list_type core_updater_list_get_type(
core_updater_list_t *core_list);
/* Fetches core updater list entry corresponding /* Fetches core updater list entry corresponding
* to the specified entry index. * to the specified entry index.
* Returns false if index is invalid. */ * Returns false if index is invalid. */
@ -138,6 +155,16 @@ bool core_updater_list_parse_network_data(
const char *network_buildbot_url, const char *network_buildbot_url,
const char *data, size_t len); const char *data, size_t len);
/* Reads the list of cores currently available
* via play feature delivery (PFD) into the
* specified core_updater_list_t object.
* Returns false in the event of an error. */
bool core_updater_list_parse_pfd_data(
core_updater_list_t *core_list,
const char *path_dir_libretro,
const char *path_libretro_info,
const struct string_list *pfd_cores);
RETRO_END_DECLS RETRO_END_DECLS
#endif #endif

View File

@ -1938,6 +1938,16 @@ static void frontend_unix_init(void *data)
"doVibrate", "(IIII)V"); "doVibrate", "(IIII)V");
GET_METHOD_ID(env, android_app->getUserLanguageString, class, GET_METHOD_ID(env, android_app->getUserLanguageString, class,
"getUserLanguageString", "()Ljava/lang/String;"); "getUserLanguageString", "()Ljava/lang/String;");
GET_METHOD_ID(env, android_app->isPlayStoreBuild, class,
"isPlayStoreBuild", "()Z");
GET_METHOD_ID(env, android_app->getAvailableCores, class,
"getAvailableCores", "()[Ljava/lang/String;");
GET_METHOD_ID(env, android_app->getInstalledCores, class,
"getInstalledCores", "()[Ljava/lang/String;");
GET_METHOD_ID(env, android_app->downloadCore, class,
"downloadCore", "(Ljava/lang/String;)V");
GET_METHOD_ID(env, android_app->deleteCore, class,
"deleteCore", "(Ljava/lang/String;)V");
CALL_OBJ_METHOD(env, obj, android_app->activity->clazz, CALL_OBJ_METHOD(env, obj, android_app->activity->clazz,
android_app->getIntent); android_app->getIntent);

View File

@ -166,6 +166,12 @@ struct android_app
jmethodID getUserLanguageString; jmethodID getUserLanguageString;
jmethodID doVibrate; jmethodID doVibrate;
jmethodID isPlayStoreBuild;
jmethodID getAvailableCores;
jmethodID getInstalledCores;
jmethodID downloadCore;
jmethodID deleteCore;
struct struct
{ {
unsigned width, height; unsigned width, height;

View File

@ -1671,3 +1671,10 @@ MISC FILE FORMATS
TIME TIME
============================================================ */ ============================================================ */
#include "../libretro-common/time/rtime.c" #include "../libretro-common/time/rtime.c"
/*============================================================
ANDROID PLAY FEATURE DELIVERY
============================================================ */
#if defined(ANDROID)
#include "../play_feature_delivery/play_feature_delivery.c"
#endif

View File

@ -10084,6 +10084,10 @@ MSG_HASH(
MSG_CORE_INSTALLED, MSG_CORE_INSTALLED,
"Core installed: " "Core installed: "
) )
MSG_HASH(
MSG_CORE_INSTALL_FAILED,
"Failed to install core: "
)
MSG_HASH( MSG_HASH(
MSG_SCANNING_CORES, MSG_SCANNING_CORES,
"Scanning cores..." "Scanning cores..."

View File

@ -89,6 +89,10 @@
#include "../../uwp/uwp_func.h" #include "../../uwp/uwp_func.h"
#endif #endif
#if defined(ANDROID)
#include "../../play_feature_delivery/play_feature_delivery.h"
#endif
enum enum
{ {
ACTION_OK_LOAD_PRESET = 0, ACTION_OK_LOAD_PRESET = 0,
@ -4021,7 +4025,9 @@ static int action_ok_core_updater_list(const char *path,
const char *label, unsigned type, size_t idx, size_t entry_idx) const char *label, unsigned type, size_t idx, size_t entry_idx)
{ {
core_updater_list_t *core_list = NULL; core_updater_list_t *core_list = NULL;
bool refresh = true; settings_t *settings = config_get_ptr();
const char *path_dir_libretro = settings->paths.directory_libretro;
const char *path_libretro_info = settings->paths.path_libretro_info;
/* Get cached core updater list, initialising /* Get cached core updater list, initialising
* it if required */ * it if required */
@ -4036,12 +4042,49 @@ static int action_ok_core_updater_list(const char *path,
return menu_cbs_exit(); return menu_cbs_exit();
} }
/* Initial setup... */ #if defined(ANDROID)
menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh); if (play_feature_delivery_enabled())
generic_action_ok_command(CMD_EVENT_NETWORK_INIT); {
/* Core downloads are handled via play
* feature delivery
* > Core list can be populated directly
* using the play feature delivery
* interface */
struct string_list *available_cores =
play_feature_delivery_available_cores();
bool success = false;
/* Push core list update task */ if (!available_cores)
task_push_get_core_updater_list(core_list, false, true); return menu_cbs_exit();
core_updater_list_reset(core_list);
success = core_updater_list_parse_pfd_data(
core_list,
path_dir_libretro,
path_libretro_info,
available_cores);
string_list_free(available_cores);
if (!success)
return menu_cbs_exit();
/* Ensure network is initialised */
generic_action_ok_command(CMD_EVENT_NETWORK_INIT);
}
else
#endif
{
bool refresh = true;
/* Initial setup... */
menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh);
generic_action_ok_command(CMD_EVENT_NETWORK_INIT);
/* Push core list update task */
task_push_get_core_updater_list(core_list, false, true);
}
return generic_action_ok_displaylist_push( return generic_action_ok_displaylist_push(
path, NULL, label, type, idx, entry_idx, path, NULL, label, type, idx, entry_idx,
@ -4466,10 +4509,18 @@ static int action_ok_core_updater_download(const char *path,
if (!core_list) if (!core_list)
return menu_cbs_exit(); return menu_cbs_exit();
task_push_core_updater_download( #if defined(ANDROID)
core_list, path, 0, false, /* Play Store builds install cores via
auto_backup, (size_t)auto_backup_history_size, * the play feature delivery interface */
path_dir_libretro, path_dir_core_assets); if (play_feature_delivery_enabled())
task_push_play_feature_delivery_core_install(
core_list, path);
else
#endif
task_push_core_updater_download(
core_list, path, 0, false,
auto_backup, (size_t)auto_backup_history_size,
path_dir_libretro, path_dir_core_assets);
#endif #endif
return 0; return 0;
@ -6677,7 +6728,24 @@ static int action_ok_core_delete(const char *path,
generic_action_ok_command(CMD_EVENT_UNLOAD_CORE); generic_action_ok_command(CMD_EVENT_UNLOAD_CORE);
/* Delete core file */ /* Delete core file */
filestream_delete(core_path); #if defined(ANDROID)
/* If this is a Play Store build and the
* core is currently installed via
* play feature delivery, must delete
* the core via the play feature delivery
* interface */
if (play_feature_delivery_enabled())
{
const char *core_filename = path_basename(core_path);
if (play_feature_delivery_core_installed(core_filename))
play_feature_delivery_delete(core_filename);
else
filestream_delete(core_path);
}
else
#endif
filestream_delete(core_path);
/* Reload core info files */ /* Reload core info files */
command_event(CMD_EVENT_CORE_INFO_INIT, NULL); command_event(CMD_EVENT_CORE_INFO_INIT, NULL);

View File

@ -62,6 +62,10 @@
#include "../frontend/drivers/platform_unix.h" #include "../frontend/drivers/platform_unix.h"
#endif #endif
#if defined(ANDROID)
#include "../play_feature_delivery/play_feature_delivery.h"
#endif
#ifdef HAVE_CDROM #ifdef HAVE_CDROM
#include <vfs/vfs_implementation_cdrom.h> #include <vfs/vfs_implementation_cdrom.h>
#include <media/media_detect_cd.h> #include <media/media_detect_cd.h>
@ -670,21 +674,28 @@ end:
/* Check whether core is currently locked */ /* Check whether core is currently locked */
bool core_locked = core_info_get_core_lock(core_path, true); bool core_locked = core_info_get_core_lock(core_path, true);
/* Lock core #if defined(ANDROID)
* > Note: Have to set core_path as both the /* Play Store builds do not support
* 'path' and 'label' parameters (otherwise * core locking */
* cannot access it in menu_cbs_get_value.c if (!play_feature_delivery_enabled())
* or menu_cbs_left/right.c), which means #endif
* entry name must be set as 'alt' text */
if (menu_entries_append_enum(info->list,
core_path,
core_path,
MENU_ENUM_LABEL_CORE_LOCK,
MENU_SETTING_ACTION_CORE_LOCK, 0, 0))
{ {
file_list_set_alt_at_offset( /* Lock core
info->list, count, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_LOCK)); * > Note: Have to set core_path as both the
count++; * 'path' and 'label' parameters (otherwise
* cannot access it in menu_cbs_get_value.c
* or menu_cbs_left/right.c), which means
* entry name must be set as 'alt' text */
if (menu_entries_append_enum(info->list,
core_path,
core_path,
MENU_ENUM_LABEL_CORE_LOCK,
MENU_SETTING_ACTION_CORE_LOCK, 0, 0))
{
file_list_set_alt_at_offset(
info->list, count, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_LOCK));
count++;
}
} }
/* Backup core */ /* Backup core */
@ -11054,12 +11065,18 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type,
MENU_SETTING_ACTION, 0, 0)) MENU_SETTING_ACTION, 0, 0))
count++; count++;
if (menu_entries_append_enum(info->list, #if defined(ANDROID)
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_UPDATE_INSTALLED_CORES), /* Play Store builds auto-update installed
msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_INSTALLED_CORES), * cores, rendering the 'update installed
MENU_ENUM_LABEL_UPDATE_INSTALLED_CORES, * cores' option irrelevant/useless */
MENU_SETTING_ACTION, 0, 0)) if (!play_feature_delivery_enabled())
count++; #endif
if (menu_entries_append_enum(info->list,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_UPDATE_INSTALLED_CORES),
msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_INSTALLED_CORES),
MENU_ENUM_LABEL_UPDATE_INSTALLED_CORES,
MENU_SETTING_ACTION, 0, 0))
count++;
} }
#endif #endif
#endif #endif

View File

@ -118,6 +118,10 @@
#include <3ds/services/cfgu.h> #include <3ds/services/cfgu.h>
#endif #endif
#if defined(ANDROID)
#include "../play_feature_delivery/play_feature_delivery.h"
#endif
#define _3_SECONDS 3000000 #define _3_SECONDS 3000000
#define _6_SECONDS 6000000 #define _6_SECONDS 6000000
#define _9_SECONDS 9000000 #define _9_SECONDS 9000000
@ -16682,21 +16686,29 @@ static bool setting_append_list(
parent_group = msg_hash_to_str(MENU_ENUM_LABEL_UPDATER_SETTINGS); parent_group = msg_hash_to_str(MENU_ENUM_LABEL_UPDATER_SETTINGS);
START_SUB_GROUP(list, list_info, "State", &group_info, &subgroup_info, parent_group); START_SUB_GROUP(list, list_info, "State", &group_info, &subgroup_info, parent_group);
#ifdef HAVE_NETWORKING #ifdef HAVE_NETWORKING
CONFIG_STRING(
list, list_info, #if defined(ANDROID)
settings->paths.network_buildbot_url, /* Play Store builds do not fetch cores
sizeof(settings->paths.network_buildbot_url), * from the buildbot */
MENU_ENUM_LABEL_CORE_UPDATER_BUILDBOT_URL, if (!play_feature_delivery_enabled())
MENU_ENUM_LABEL_VALUE_CORE_UPDATER_BUILDBOT_URL, #endif
DEFAULT_BUILDBOT_SERVER_URL, {
&group_info, CONFIG_STRING(
&subgroup_info, list, list_info,
parent_group, settings->paths.network_buildbot_url,
general_write_handler, sizeof(settings->paths.network_buildbot_url),
general_read_handler); MENU_ENUM_LABEL_CORE_UPDATER_BUILDBOT_URL,
SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ALLOW_INPUT); MENU_ENUM_LABEL_VALUE_CORE_UPDATER_BUILDBOT_URL,
(*list)[list_info->index - 1].ui_type = ST_UI_TYPE_STRING_LINE_EDIT; DEFAULT_BUILDBOT_SERVER_URL,
(*list)[list_info->index - 1].action_start = setting_generic_action_start_default; &group_info,
&subgroup_info,
parent_group,
general_write_handler,
general_read_handler);
SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ALLOW_INPUT);
(*list)[list_info->index - 1].ui_type = ST_UI_TYPE_STRING_LINE_EDIT;
(*list)[list_info->index - 1].action_start = setting_generic_action_start_default;
}
CONFIG_STRING( CONFIG_STRING(
list, list_info, list, list_info,
@ -16746,37 +16758,44 @@ static bool setting_append_list(
SD_FLAG_NONE SD_FLAG_NONE
); );
CONFIG_BOOL( #if defined(ANDROID)
list, list_info, /* Play Store builds do not support automatic
&settings->bools.core_updater_auto_backup, * core backups */
MENU_ENUM_LABEL_CORE_UPDATER_AUTO_BACKUP, if (!play_feature_delivery_enabled())
MENU_ENUM_LABEL_VALUE_CORE_UPDATER_AUTO_BACKUP, #endif
DEFAULT_CORE_UPDATER_AUTO_BACKUP, {
MENU_ENUM_LABEL_VALUE_OFF, CONFIG_BOOL(
MENU_ENUM_LABEL_VALUE_ON,
&group_info,
&subgroup_info,
parent_group,
general_write_handler,
general_read_handler,
SD_FLAG_NONE
);
CONFIG_UINT(
list, list_info, list, list_info,
&settings->uints.core_updater_auto_backup_history_size, &settings->bools.core_updater_auto_backup,
MENU_ENUM_LABEL_CORE_UPDATER_AUTO_BACKUP_HISTORY_SIZE, MENU_ENUM_LABEL_CORE_UPDATER_AUTO_BACKUP,
MENU_ENUM_LABEL_VALUE_CORE_UPDATER_AUTO_BACKUP_HISTORY_SIZE, MENU_ENUM_LABEL_VALUE_CORE_UPDATER_AUTO_BACKUP,
DEFAULT_CORE_UPDATER_AUTO_BACKUP_HISTORY_SIZE, DEFAULT_CORE_UPDATER_AUTO_BACKUP,
MENU_ENUM_LABEL_VALUE_OFF,
MENU_ENUM_LABEL_VALUE_ON,
&group_info, &group_info,
&subgroup_info, &subgroup_info,
parent_group, parent_group,
general_write_handler, general_write_handler,
general_read_handler); general_read_handler,
(*list)[list_info->index - 1].ui_type = ST_UI_TYPE_UINT_COMBOBOX; SD_FLAG_NONE
(*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; );
(*list)[list_info->index - 1].offset_by = 1;
menu_settings_list_current_add_range(list, list_info, (*list)[list_info->index - 1].offset_by, 500, 1, true, true); CONFIG_UINT(
list, list_info,
&settings->uints.core_updater_auto_backup_history_size,
MENU_ENUM_LABEL_CORE_UPDATER_AUTO_BACKUP_HISTORY_SIZE,
MENU_ENUM_LABEL_VALUE_CORE_UPDATER_AUTO_BACKUP_HISTORY_SIZE,
DEFAULT_CORE_UPDATER_AUTO_BACKUP_HISTORY_SIZE,
&group_info,
&subgroup_info,
parent_group,
general_write_handler,
general_read_handler);
(*list)[list_info->index - 1].ui_type = ST_UI_TYPE_UINT_COMBOBOX;
(*list)[list_info->index - 1].action_ok = &setting_action_ok_uint;
(*list)[list_info->index - 1].offset_by = 1;
menu_settings_list_current_add_range(list, list_info, (*list)[list_info->index - 1].offset_by, 500, 1, true, true);
}
#endif #endif
END_SUB_GROUP(list, list_info, parent_group); END_SUB_GROUP(list, list_info, parent_group);
END_GROUP(list, list_info, parent_group); END_GROUP(list, list_info, parent_group);

View File

@ -2073,6 +2073,7 @@ enum msg_hash_enums
MSG_DOWNLOADING_CORE, MSG_DOWNLOADING_CORE,
MSG_EXTRACTING_CORE, MSG_EXTRACTING_CORE,
MSG_CORE_INSTALLED, MSG_CORE_INSTALLED,
MSG_CORE_INSTALL_FAILED,
MSG_SCANNING_CORES, MSG_SCANNING_CORES,
MSG_CHECKING_CORE, MSG_CHECKING_CORE,
MSG_ALL_CORES_UPDATED, MSG_ALL_CORES_UPDATED,

View File

@ -0,0 +1,89 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_retroarch_browser_retroactivity_RetroActivityCommon */
#ifndef _Included_com_retroarch_browser_retroactivity_RetroActivityCommon
#define _Included_com_retroarch_browser_retroactivity_RetroActivityCommon
#ifdef __cplusplus
extern "C" {
#endif
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_ABOVE_CLIENT
#define com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_ABOVE_CLIENT 8L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_ADJUST_WITH_ACTIVITY
#define com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_ADJUST_WITH_ACTIVITY 128L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_ALLOW_OOM_MANAGEMENT
#define com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_ALLOW_OOM_MANAGEMENT 16L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_AUTO_CREATE
#define com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_AUTO_CREATE 1L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_DEBUG_UNBIND
#define com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_DEBUG_UNBIND 2L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_EXTERNAL_SERVICE
#define com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_EXTERNAL_SERVICE -2147483648L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_IMPORTANT
#define com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_IMPORTANT 64L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_INCLUDE_CAPABILITIES
#define com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_INCLUDE_CAPABILITIES 4096L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_NOT_FOREGROUND
#define com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_NOT_FOREGROUND 4L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_NOT_PERCEPTIBLE
#define com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_NOT_PERCEPTIBLE 256L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_WAIVE_PRIORITY
#define com_retroarch_browser_retroactivity_RetroActivityCommon_BIND_WAIVE_PRIORITY 32L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_CONTEXT_IGNORE_SECURITY
#define com_retroarch_browser_retroactivity_RetroActivityCommon_CONTEXT_IGNORE_SECURITY 2L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_CONTEXT_INCLUDE_CODE
#define com_retroarch_browser_retroactivity_RetroActivityCommon_CONTEXT_INCLUDE_CODE 1L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_CONTEXT_RESTRICTED
#define com_retroarch_browser_retroactivity_RetroActivityCommon_CONTEXT_RESTRICTED 4L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_MODE_APPEND
#define com_retroarch_browser_retroactivity_RetroActivityCommon_MODE_APPEND 32768L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_MODE_ENABLE_WRITE_AHEAD_LOGGING
#define com_retroarch_browser_retroactivity_RetroActivityCommon_MODE_ENABLE_WRITE_AHEAD_LOGGING 8L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_MODE_MULTI_PROCESS
#define com_retroarch_browser_retroactivity_RetroActivityCommon_MODE_MULTI_PROCESS 4L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_MODE_NO_LOCALIZED_COLLATORS
#define com_retroarch_browser_retroactivity_RetroActivityCommon_MODE_NO_LOCALIZED_COLLATORS 16L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_MODE_PRIVATE
#define com_retroarch_browser_retroactivity_RetroActivityCommon_MODE_PRIVATE 0L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_MODE_WORLD_READABLE
#define com_retroarch_browser_retroactivity_RetroActivityCommon_MODE_WORLD_READABLE 1L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_MODE_WORLD_WRITEABLE
#define com_retroarch_browser_retroactivity_RetroActivityCommon_MODE_WORLD_WRITEABLE 2L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_RECEIVER_VISIBLE_TO_INSTANT_APPS
#define com_retroarch_browser_retroactivity_RetroActivityCommon_RECEIVER_VISIBLE_TO_INSTANT_APPS 1L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_DEFAULT_KEYS_DIALER
#define com_retroarch_browser_retroactivity_RetroActivityCommon_DEFAULT_KEYS_DIALER 1L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_DEFAULT_KEYS_DISABLE
#define com_retroarch_browser_retroactivity_RetroActivityCommon_DEFAULT_KEYS_DISABLE 0L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_DEFAULT_KEYS_SEARCH_GLOBAL
#define com_retroarch_browser_retroactivity_RetroActivityCommon_DEFAULT_KEYS_SEARCH_GLOBAL 4L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_DEFAULT_KEYS_SEARCH_LOCAL
#define com_retroarch_browser_retroactivity_RetroActivityCommon_DEFAULT_KEYS_SEARCH_LOCAL 3L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_DEFAULT_KEYS_SHORTCUT
#define com_retroarch_browser_retroactivity_RetroActivityCommon_DEFAULT_KEYS_SHORTCUT 2L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_RESULT_CANCELED
#define com_retroarch_browser_retroactivity_RetroActivityCommon_RESULT_CANCELED 0L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_RESULT_FIRST_USER
#define com_retroarch_browser_retroactivity_RetroActivityCommon_RESULT_FIRST_USER 1L
#undef com_retroarch_browser_retroactivity_RetroActivityCommon_RESULT_OK
#define com_retroarch_browser_retroactivity_RetroActivityCommon_RESULT_OK -1L
/*
* Class: com_retroarch_browser_retroactivity_RetroActivityCommon
* Method: coreInstallInitiated
* Signature: (Ljava/lang/String;Z)V
*/
JNIEXPORT void JNICALL Java_com_retroarch_browser_retroactivity_RetroActivityCommon_coreInstallInitiated
(JNIEnv *, jobject, jstring, jboolean);
/*
* Class: com_retroarch_browser_retroactivity_RetroActivityCommon
* Method: coreInstallStatusChanged
* Signature: ([Ljava/lang/String;IJJ)V
*/
JNIEXPORT void JNICALL Java_com_retroarch_browser_retroactivity_RetroActivityCommon_coreInstallStatusChanged
(JNIEnv *, jobject, jobjectArray, jint, jlong, jlong);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,528 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2011-2017 - Daniel De Matteis
* Copyright (C) 2014-2017 - Jean-André Santoni
* Copyright (C) 2016-2019 - Brad Parker
* Copyright (C) 2019-2020 - James Leaver
*
* 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 "com_retroarch_browser_retroactivity_RetroActivityCommon.h"
#ifdef HAVE_THREADS
#include <rthreads/rthreads.h>
#include <retro_assert.h>
#include <stdlib.h>
#endif
#include <string/stdstring.h>
#include "../frontend/drivers/platform_unix.h"
#include "play_feature_delivery.h"
/***************************/
/* Globals (do not fix...) */
/***************************/
/* Due to the way the JNI interface works,
* core download status updates happen
* asynchronously in a manner that we cannot
* capture using any standard means. We therefore
* have to implement status monitoring via an
* ugly hack, involving a mutex-locked global
* status struct... */
typedef struct
{
#ifdef HAVE_THREADS
slock_t *lock;
#endif
unsigned download_progress;
enum play_feature_delivery_install_status last_status;
char last_core_name[256];
bool active;
} play_feature_delivery_state_t;
static play_feature_delivery_state_t play_feature_delivery_state = {
#ifdef HAVE_THREADS
NULL, /* lock */
#endif
0, /* download_progress */
PLAY_FEATURE_DELIVERY_IDLE, /* last_status */
{'\0'}, /* last_core_name */
false, /* active */
};
static play_feature_delivery_state_t* play_feature_delivery_get_state(void)
{
return &play_feature_delivery_state;
}
/**********************/
/* JNI Native Methods */
/**********************/
/*
* Class: com_retroarch_browser_retroactivity_RetroActivityCommon
* Method: coreInstallInitiated
* Signature: (Ljava/lang/String;Z)V
*/
JNIEXPORT void JNICALL Java_com_retroarch_browser_retroactivity_RetroActivityCommon_coreInstallInitiated
(JNIEnv *env, jobject this_obj, jstring core_name, jboolean successful)
{
play_feature_delivery_state_t* state = play_feature_delivery_get_state();
const char *core_name_c = NULL;
/* Lock mutex */
#ifdef HAVE_THREADS
slock_lock(state->lock);
#endif
/* Only update status if an install is active */
if (state->active)
{
/* Convert Java-style string to a proper char array */
const char *core_name_c = (*env)->GetStringUTFChars(
env, core_name, NULL);
/* Ensure that status update is for the
* correct core */
if (string_is_equal(state->last_core_name, core_name_c))
{
if (successful)
state->last_status = PLAY_FEATURE_DELIVERY_STARTING;
else
{
state->last_status = PLAY_FEATURE_DELIVERY_FAILED;
state->active = false;
}
}
/* Must always 'release' the converted string */
(*env)->ReleaseStringUTFChars(env, core_name, core_name_c);
}
/* Unlock mutex */
#ifdef HAVE_THREADS
slock_unlock(state->lock);
#endif
}
/*
* Class: com_retroarch_browser_retroactivity_RetroActivityCommon
* Method: coreInstallStatusChanged
* Signature: ([Ljava/lang/String;IJJ)V
*/
JNIEXPORT void JNICALL Java_com_retroarch_browser_retroactivity_RetroActivityCommon_coreInstallStatusChanged
(JNIEnv *env, jobject thisObj, jobjectArray core_names, jint status, jlong bytes_downloaded, jlong total_bytes_to_download)
{
play_feature_delivery_state_t* state = play_feature_delivery_get_state();
/* Lock mutex */
#ifdef HAVE_THREADS
slock_lock(state->lock);
#endif
/* Only update status if an install is active */
if (state->active)
{
/* Note: core_names is a list of cores that
* are currently installing. We should check
* that state->last_core_name is in this list
* before updating the status, but if multiple
* installs are queued then it seems dubious
* to filter like this - i.e. is it possible
* for an entry to drop off the queue before
* the entire transaction is complete? If so,
* then we may risk 'missing' the final status
* update...
* We therefore just monitor the transaction
* as a whole, and disregard core names... */
/* Determine download progress */
if (total_bytes_to_download > 0)
{
state->download_progress = (unsigned)
(((float)bytes_downloaded * 100.0f /
(float)total_bytes_to_download) + 0.5f);
state->download_progress = (state->download_progress > 100) ?
100 : state->download_progress;
}
else
state->download_progress = 100;
/* Check status */
switch (status)
{
case 0: /* INSTALL_STATUS_DOWNLOADING */
state->last_status = PLAY_FEATURE_DELIVERY_DOWNLOADING;
break;
case 1: /* INSTALL_STATUS_INSTALLING */
state->last_status = PLAY_FEATURE_DELIVERY_INSTALLING;
break;
case 2: /* INSTALL_STATUS_INSTALLED */
state->last_status = PLAY_FEATURE_DELIVERY_INSTALLED;
state->active = false;
break;
case 3: /* INSTALL_STATUS_FAILED */
default:
state->last_status = PLAY_FEATURE_DELIVERY_FAILED;
state->active = false;
break;
}
}
/* Unlock mutex */
#ifdef HAVE_THREADS
slock_unlock(state->lock);
#endif
}
/******************/
/* Initialisation */
/******************/
/* Must be called upon program initialisation */
void play_feature_delivery_init(void)
{
play_feature_delivery_state_t* state = play_feature_delivery_get_state();
play_feature_delivery_deinit();
#ifdef HAVE_THREADS
if (!state->lock)
state->lock = slock_new();
retro_assert(state->lock);
#endif
}
/* Must be called upon program termination */
void play_feature_delivery_deinit(void)
{
play_feature_delivery_state_t* state = play_feature_delivery_get_state();
#ifdef HAVE_THREADS
if (state->lock)
{
slock_free(state->lock);
state->lock = NULL;
}
#endif
}
/**********/
/* Status */
/**********/
static bool play_feature_delivery_get_core_name(
const char *core_file, char *core_name, size_t len)
{
size_t core_file_len;
if (string_is_empty(core_file))
return false;
core_file_len = strlen(core_file);
if (len < core_file_len)
return false;
/* Ensure that core_file has the correct
* suffix */
if (!string_ends_with_size(core_file, "_libretro_android.so",
core_file_len, STRLEN_CONST("_libretro_android.so")))
return false;
/* Copy core_file and remove suffix */
strlcpy(core_name, core_file, len);
core_name[core_file_len - STRLEN_CONST("_libretro_android.so")] = '\0';
return true;
}
/* Returns true if current build utilises
* play feature delivery */
bool play_feature_delivery_enabled(void)
{
JNIEnv *env = jni_thread_getenv();
struct android_app *app = (struct android_app*)g_android;
bool enabled = false;
if (!env ||
!app ||
!app->isPlayStoreBuild)
return false;
CALL_BOOLEAN_METHOD(env, enabled, app->activity->clazz,
app->isPlayStoreBuild);
return enabled;
}
/* Returns a list of cores currently available
* via play feature delivery.
* Returns a new string_list on success, or
* NULL on failure */
struct string_list *play_feature_delivery_available_cores(void)
{
JNIEnv *env = jni_thread_getenv();
struct android_app *app = (struct android_app*)g_android;
struct string_list *core_list = string_list_new();
union string_list_elem_attr attr;
jobjectArray available_cores;
jsize num_cores;
size_t i;
attr.i = 0;
if (!env ||
!app ||
!app->getAvailableCores ||
!core_list)
goto error;
/* Get list of available cores */
CALL_OBJ_METHOD(env, available_cores, app->activity->clazz,
app->getAvailableCores);
num_cores = (*env)->GetArrayLength(env, available_cores);
for (i = 0; i < num_cores; i++)
{
/* Extract element of available cores array */
jstring core_name_jni = (jstring)
((*env)->GetObjectArrayElement(env, available_cores, i));
const char *core_name = NULL;
/* Convert Java-style string to a proper char array */
core_name = (*env)->GetStringUTFChars(env, core_name_jni, NULL);
if (!string_is_empty(core_name))
{
char core_file[256];
core_file[0] = '\0';
/* Generate core file name */
strlcpy(core_file, core_name, sizeof(core_file));
strlcat(core_file, "_libretro_android.so", sizeof(core_file));
/* Add entry to list */
if (!string_is_empty(core_file))
string_list_append(core_list, core_file, attr);
}
/* Must always 'release' the converted string */
(*env)->ReleaseStringUTFChars(env, core_name_jni, core_name);
}
if (core_list->size < 1)
goto error;
return core_list;
error:
if (core_list)
string_list_free(core_list);
return NULL;
}
/* Returns true if specified core is currently
* installed via play feature delivery */
bool play_feature_delivery_core_installed(const char *core_file)
{
JNIEnv *env = jni_thread_getenv();
struct android_app *app = (struct android_app*)g_android;
jobjectArray installed_cores;
jsize num_cores;
char core_name[256];
size_t i;
core_name[0] = '\0';
if (!env ||
!app ||
!app->getInstalledCores)
return false;
/* Extract core name */
if (!play_feature_delivery_get_core_name(
core_file, core_name, sizeof(core_name)))
return false;
/* Get list of installed cores */
CALL_OBJ_METHOD(env, installed_cores, app->activity->clazz,
app->getInstalledCores);
num_cores = (*env)->GetArrayLength(env, installed_cores);
for (i = 0; i < num_cores; i++)
{
/* Extract element of installed cores array */
jstring installed_core_name_jni = (jstring)
((*env)->GetObjectArrayElement(env, installed_cores, i));
const char *installed_core_name = NULL;
/* Convert Java-style string to a proper char array */
installed_core_name = (*env)->GetStringUTFChars(
env, installed_core_name_jni, NULL);
/* Check for a match */
if (!string_is_empty(installed_core_name) &&
string_is_equal(core_name, installed_core_name))
{
/* Must always 'release' the converted string */
(*env)->ReleaseStringUTFChars(env,
installed_core_name_jni, installed_core_name);
return true;
}
/* Must always 'release' the converted string */
(*env)->ReleaseStringUTFChars(env,
installed_core_name_jni, installed_core_name);
}
return false;
}
/* Fetches last recorded status of the most
* recently initiated play feature delivery
* install transaction.
* 'progress' is an integer from 0-100.
* Returns true if a transaction is currently
* in progress. */
bool play_feature_delivery_download_status(
enum play_feature_delivery_install_status *status,
unsigned *progress)
{
play_feature_delivery_state_t* state = play_feature_delivery_get_state();
bool active;
/* Lock mutex */
#ifdef HAVE_THREADS
slock_lock(state->lock);
#endif
/* Copy status parameters */
if (status)
*status = state->last_status;
if (progress)
*progress = state->download_progress;
active = state->active;
/* Unlock mutex */
#ifdef HAVE_THREADS
slock_unlock(state->lock);
#endif
return active;
}
/***********/
/* Control */
/***********/
/* Initialises download of the specified core.
* Returns false in the event of an error.
* Download status should be monitored via
* play_feature_delivery_download_status() */
bool play_feature_delivery_download(const char *core_file)
{
play_feature_delivery_state_t* state = play_feature_delivery_get_state();
JNIEnv *env = jni_thread_getenv();
struct android_app *app = (struct android_app*)g_android;
bool success = false;
char core_name[256];
jstring core_name_jni;
core_name[0] = '\0';
if (!env ||
!app ||
!app->downloadCore)
return false;
/* Extract core name */
if (!play_feature_delivery_get_core_name(
core_file, core_name, sizeof(core_name)))
return false;
/* Lock mutex */
#ifdef HAVE_THREADS
slock_lock(state->lock);
#endif
/* We only support one download at a time */
if (!state->active)
{
/* Convert to a Java-style string */
core_name_jni = (*env)->NewStringUTF(env, core_name);
/* Request download */
CALL_VOID_METHOD_PARAM(env, app->activity->clazz,
app->downloadCore, core_name_jni);
/* Free core_name_jni reference */
(*env)->DeleteLocalRef(env, core_name_jni);
/* Update status */
state->download_progress = 0;
state->last_status = PLAY_FEATURE_DELIVERY_PENDING;
state->active = true;
strlcpy(state->last_core_name, core_name,
sizeof(state->last_core_name));
success = true;
}
/* Unlock mutex */
#ifdef HAVE_THREADS
slock_unlock(state->lock);
#endif
return success;
}
/* Deletes specified core.
* Returns false in the event of an error. */
bool play_feature_delivery_delete(const char *core_file)
{
JNIEnv *env = jni_thread_getenv();
struct android_app *app = (struct android_app*)g_android;
char core_name[256];
jstring core_name_jni;
core_name[0] = '\0';
if (!env ||
!app ||
!app->deleteCore)
return false;
/* Extract core name */
if (!play_feature_delivery_get_core_name(
core_file, core_name, sizeof(core_name)))
return false;
/* Convert to a Java-style string */
core_name_jni = (*env)->NewStringUTF(env, core_name);
/* Request core deletion */
CALL_VOID_METHOD_PARAM(env, app->activity->clazz,
app->deleteCore, core_name_jni);
/* Free core_name_jni reference */
(*env)->DeleteLocalRef(env, core_name_jni);
return true;
}

View File

@ -0,0 +1,99 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2011-2017 - Daniel De Matteis
* Copyright (C) 2014-2017 - Jean-André Santoni
* Copyright (C) 2016-2019 - Brad Parker
* Copyright (C) 2019-2020 - James Leaver
*
* 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/>.
*/
#ifndef __PLAY_FEATURE_DELIVERY_H
#define __PLAY_FEATURE_DELIVERY_H
#include <retro_common_api.h>
#include <libretro.h>
#include <lists/string_list.h>
#include <boolean.h>
RETRO_BEGIN_DECLS
/* Defines possible status values of
* a play feature delivery install
* transaction */
enum play_feature_delivery_install_status
{
PLAY_FEATURE_DELIVERY_IDLE = 0,
PLAY_FEATURE_DELIVERY_PENDING,
PLAY_FEATURE_DELIVERY_STARTING,
PLAY_FEATURE_DELIVERY_DOWNLOADING,
PLAY_FEATURE_DELIVERY_INSTALLING,
PLAY_FEATURE_DELIVERY_INSTALLED,
PLAY_FEATURE_DELIVERY_FAILED
};
/******************/
/* Initialisation */
/******************/
/* Must be called upon program initialisation */
void play_feature_delivery_init(void);
/* Must be called upon program termination */
void play_feature_delivery_deinit(void);
/**********/
/* Status */
/**********/
/* Returns true if current build utilises
* play feature delivery */
bool play_feature_delivery_enabled(void);
/* Returns a list of cores currently available
* via play feature delivery.
* Returns a new string_list on success, or
* NULL on failure */
struct string_list *play_feature_delivery_available_cores(void);
/* Returns true if specified core is currently
* installed via play feature delivery */
bool play_feature_delivery_core_installed(const char *core_file);
/* Fetches last recorded status of the most
* recently initiated play feature delivery
* install transaction.
* 'progress' is an integer from 0-100.
* Returns true if a transaction is currently
* in progress. */
bool play_feature_delivery_download_status(
enum play_feature_delivery_install_status *status,
unsigned *progress);
/***********/
/* Control */
/***********/
/* Initialises download of the specified core.
* Returns false in the event of an error.
* Download status should be monitored via
* play_feature_delivery_download_status() */
bool play_feature_delivery_download(const char *core_file);
/* Deletes specified core.
* Returns false in the event of an error. */
bool play_feature_delivery_delete(const char *core_file);
RETRO_END_DECLS
#endif

View File

@ -120,6 +120,10 @@
#include "switch_performance_profiles.h" #include "switch_performance_profiles.h"
#endif #endif
#if defined(ANDROID)
#include "play_feature_delivery/play_feature_delivery.h"
#endif
#ifdef HAVE_DISCORD #ifdef HAVE_DISCORD
#include <discord_rpc.h> #include <discord_rpc.h>
#include "deps/discord-rpc/include/discord_rpc.h" #include "deps/discord-rpc/include/discord_rpc.h"
@ -17539,6 +17543,10 @@ void main_exit(void *args)
rtime_deinit(); rtime_deinit();
#if defined(ANDROID)
play_feature_delivery_deinit();
#endif
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__) #if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
CoUninitialize(); CoUninitialize();
#endif #endif
@ -17577,6 +17585,10 @@ int rarch_main(int argc, char *argv[], void *data)
rtime_init(); rtime_init();
#if defined(ANDROID)
play_feature_delivery_init();
#endif
libretro_free_system_info(&p_rarch->runloop_system.info); libretro_free_system_info(&p_rarch->runloop_system.info);
command_event(CMD_EVENT_HISTORY_DEINIT, NULL); command_event(CMD_EVENT_HISTORY_DEINIT, NULL);
rarch_favorites_deinit(); rarch_favorites_deinit();

View File

@ -36,6 +36,10 @@
#include "../core_info.h" #include "../core_info.h"
#include "../core_backup.h" #include "../core_backup.h"
#if defined(ANDROID)
#include "../play_feature_delivery/play_feature_delivery.h"
#endif
#define CORE_BACKUP_CHUNK_SIZE 4096 #define CORE_BACKUP_CHUNK_SIZE 4096
enum core_backup_status enum core_backup_status
@ -778,6 +782,27 @@ static void task_core_restore_handler(retro_task_t *task)
break; break;
} }
#if defined(ANDROID)
/* If this is a Play Store build and the
* core is currently installed via
* play feature delivery, must delete
* the existing core before attempting
* to write any data */
if (play_feature_delivery_enabled())
{
const char *core_filename = path_basename(
backup_handle->core_path);
if (play_feature_delivery_core_installed(core_filename) &&
!play_feature_delivery_delete(core_filename))
{
RARCH_ERR("[core restore] Failed to delete existing play feature delivery core: %s\n",
backup_handle->core_path);
backup_handle->status = CORE_RESTORE_END;
break;
}
}
#endif
/* Open core file for writing */ /* Open core file for writing */
backup_handle->core_file = intfstream_open_file( backup_handle->core_file = intfstream_open_file(
backup_handle->core_path, RETRO_VFS_FILE_ACCESS_WRITE, backup_handle->core_path, RETRO_VFS_FILE_ACCESS_WRITE,

View File

@ -38,6 +38,10 @@
#include "../verbosity.h" #include "../verbosity.h"
#include "../core_updater_list.h" #include "../core_updater_list.h"
#if defined(ANDROID)
#include "../play_feature_delivery/play_feature_delivery.h"
#endif
#if defined(RARCH_INTERNAL) && defined(HAVE_MENU) #if defined(RARCH_INTERNAL) && defined(HAVE_MENU)
#include "../menu/menu_entries.h" #include "../menu/menu_entries.h"
#endif #endif
@ -50,29 +54,6 @@ enum core_updater_list_status
CORE_UPDATER_LIST_END CORE_UPDATER_LIST_END
}; };
/* Download core */
enum core_updater_download_status
{
CORE_UPDATER_DOWNLOAD_BEGIN = 0,
CORE_UPDATER_DOWNLOAD_START_BACKUP,
CORE_UPDATER_DOWNLOAD_WAIT_BACKUP,
CORE_UPDATER_DOWNLOAD_START_TRANSFER,
CORE_UPDATER_DOWNLOAD_WAIT_TRANSFER,
CORE_UPDATER_DOWNLOAD_WAIT_DECOMPRESS,
CORE_UPDATER_DOWNLOAD_END
};
/* Update installed cores */
enum update_installed_cores_status
{
UPDATE_INSTALLED_CORES_BEGIN = 0,
UPDATE_INSTALLED_CORES_WAIT_LIST,
UPDATE_INSTALLED_CORES_ITERATE,
UPDATE_INSTALLED_CORES_UPDATE_CORE,
UPDATE_INSTALLED_CORES_WAIT_DOWNLOAD,
UPDATE_INSTALLED_CORES_END
};
typedef struct core_updater_list_handle typedef struct core_updater_list_handle
{ {
core_updater_list_t* core_list; core_updater_list_t* core_list;
@ -85,6 +66,18 @@ typedef struct core_updater_list_handle
bool http_task_success; bool http_task_success;
} core_updater_list_handle_t; } core_updater_list_handle_t;
/* Download core */
enum core_updater_download_status
{
CORE_UPDATER_DOWNLOAD_BEGIN = 0,
CORE_UPDATER_DOWNLOAD_START_BACKUP,
CORE_UPDATER_DOWNLOAD_WAIT_BACKUP,
CORE_UPDATER_DOWNLOAD_START_TRANSFER,
CORE_UPDATER_DOWNLOAD_WAIT_TRANSFER,
CORE_UPDATER_DOWNLOAD_WAIT_DECOMPRESS,
CORE_UPDATER_DOWNLOAD_END
};
typedef struct core_updater_download_handle typedef struct core_updater_download_handle
{ {
char *path_dir_libretro; char *path_dir_libretro;
@ -110,6 +103,17 @@ typedef struct core_updater_download_handle
bool backup_enabled; bool backup_enabled;
} core_updater_download_handle_t; } core_updater_download_handle_t;
/* Update installed cores */
enum update_installed_cores_status
{
UPDATE_INSTALLED_CORES_BEGIN = 0,
UPDATE_INSTALLED_CORES_WAIT_LIST,
UPDATE_INSTALLED_CORES_ITERATE,
UPDATE_INSTALLED_CORES_UPDATE_CORE,
UPDATE_INSTALLED_CORES_WAIT_DOWNLOAD,
UPDATE_INSTALLED_CORES_END
};
typedef struct update_installed_cores_handle typedef struct update_installed_cores_handle
{ {
char *path_dir_libretro; char *path_dir_libretro;
@ -127,6 +131,26 @@ typedef struct update_installed_cores_handle
bool auto_backup; bool auto_backup;
} update_installed_cores_handle_t; } update_installed_cores_handle_t;
/* Play feature delivery core install */
#if defined(ANDROID)
enum play_feature_delivery_install_task_status
{
PLAY_FEATURE_DELIVERY_INSTALL_BEGIN = 0,
PLAY_FEATURE_DELIVERY_INSTALL_WAIT,
PLAY_FEATURE_DELIVERY_INSTALL_END
};
typedef struct play_feature_delivery_install_handle
{
char *core_filename;
char *local_core_path;
char *display_name;
enum play_feature_delivery_install_task_status status;
bool success;
bool core_already_installed;
} play_feature_delivery_install_handle_t;
#endif
/*********************/ /*********************/
/* Utility functions */ /* Utility functions */
/*********************/ /*********************/
@ -393,6 +417,13 @@ void *task_push_get_core_updater_list(
core_updater_list_handle_t *list_handle = (core_updater_list_handle_t*) core_updater_list_handle_t *list_handle = (core_updater_list_handle_t*)
calloc(1, sizeof(core_updater_list_handle_t)); calloc(1, sizeof(core_updater_list_handle_t));
#if defined(ANDROID)
/* Regular core updater is disabled in
* Play Store builds */
if (play_feature_delivery_enabled())
goto error;
#endif
/* Sanity check */ /* Sanity check */
if (!core_list || !list_handle) if (!core_list || !list_handle)
goto error; goto error;
@ -952,6 +983,13 @@ void *task_push_core_updater_download(
task_title[0] = '\0'; task_title[0] = '\0';
local_download_path[0] = '\0'; local_download_path[0] = '\0';
#if defined(ANDROID)
/* Regular core updater is disabled in
* Play Store builds */
if (play_feature_delivery_enabled())
goto error;
#endif
/* Sanity check */ /* Sanity check */
if (!core_list || if (!core_list ||
string_is_empty(filename) || string_is_empty(filename) ||
@ -1410,6 +1448,13 @@ void task_push_update_installed_cores(
(update_installed_cores_handle_t*) (update_installed_cores_handle_t*)
calloc(1, sizeof(update_installed_cores_handle_t)); calloc(1, sizeof(update_installed_cores_handle_t));
#if defined(ANDROID)
/* Regular core updater is disabled in
* Play Store builds */
if (play_feature_delivery_enabled())
goto error;
#endif
/* Sanity check */ /* Sanity check */
if (!update_installed_handle || if (!update_installed_handle ||
string_is_empty(path_dir_libretro)) string_is_empty(path_dir_libretro))
@ -1471,3 +1516,265 @@ error:
/* Clean up handle */ /* Clean up handle */
free_update_installed_cores_handle(update_installed_handle); free_update_installed_cores_handle(update_installed_handle);
} }
/**************************************/
/* Play feature delivery core install */
/**************************************/
#if defined(ANDROID)
static void free_play_feature_delivery_install_handle(
play_feature_delivery_install_handle_t *pfd_install_handle)
{
if (!pfd_install_handle)
return;
if (pfd_install_handle->core_filename)
free(pfd_install_handle->core_filename);
if (pfd_install_handle->local_core_path)
free(pfd_install_handle->local_core_path);
if (pfd_install_handle->display_name)
free(pfd_install_handle->display_name);
free(pfd_install_handle);
pfd_install_handle = NULL;
}
static void task_play_feature_delivery_core_install_handler(retro_task_t *task)
{
play_feature_delivery_install_handle_t *pfd_install_handle = NULL;
if (!task)
goto task_finished;
pfd_install_handle = (play_feature_delivery_install_handle_t*)task->state;
if (!pfd_install_handle)
goto task_finished;
if (task_get_cancelled(task))
goto task_finished;
switch (pfd_install_handle->status)
{
case PLAY_FEATURE_DELIVERY_INSTALL_BEGIN:
{
/* Check whether core has already been
* installed via play feature delivery */
if (play_feature_delivery_core_installed(
pfd_install_handle->core_filename))
{
pfd_install_handle->success = true;
pfd_install_handle->core_already_installed = true;
pfd_install_handle->status =
PLAY_FEATURE_DELIVERY_INSTALL_END;
break;
}
/* If core is already installed via other
* means, must delete it before attempting
* play feature delivery transaction */
if (path_is_valid(pfd_install_handle->local_core_path))
filestream_delete(pfd_install_handle->local_core_path);
/* Start download */
if (play_feature_delivery_download(
pfd_install_handle->core_filename))
pfd_install_handle->status = PLAY_FEATURE_DELIVERY_INSTALL_WAIT;
else
pfd_install_handle->status = PLAY_FEATURE_DELIVERY_INSTALL_END;
}
break;
case PLAY_FEATURE_DELIVERY_INSTALL_WAIT:
{
bool install_active;
enum play_feature_delivery_install_status install_status;
unsigned install_progress;
char task_title[PATH_MAX_LENGTH];
task_title[0] = '\0';
/* Get current install status */
install_active = play_feature_delivery_download_status(
&install_status, &install_progress);
/* In all cases, update task progress */
task_set_progress(task, install_progress);
/* Interpret status */
switch (install_status)
{
case PLAY_FEATURE_DELIVERY_INSTALLED:
pfd_install_handle->success = true;
pfd_install_handle->status = PLAY_FEATURE_DELIVERY_INSTALL_END;
break;
case PLAY_FEATURE_DELIVERY_FAILED:
pfd_install_handle->status = PLAY_FEATURE_DELIVERY_INSTALL_END;
break;
case PLAY_FEATURE_DELIVERY_DOWNLOADING:
task_free_title(task);
strlcpy(task_title, msg_hash_to_str(MSG_DOWNLOADING_CORE),
sizeof(task_title));
strlcat(task_title, pfd_install_handle->display_name,
sizeof(task_title));
task_set_title(task, strdup(task_title));
break;
case PLAY_FEATURE_DELIVERY_INSTALLING:
task_free_title(task);
strlcpy(task_title, msg_hash_to_str(MSG_INSTALLING_CORE),
sizeof(task_title));
strlcat(task_title, pfd_install_handle->display_name,
sizeof(task_title));
task_set_title(task, strdup(task_title));
break;
default:
break;
}
/* If install is inactive, end task (regardless
* of status) */
if (!install_active)
pfd_install_handle->status = PLAY_FEATURE_DELIVERY_INSTALL_END;
}
break;
case PLAY_FEATURE_DELIVERY_INSTALL_END:
{
const char *msg_str = msg_hash_to_str(MSG_CORE_INSTALL_FAILED);
char task_title[PATH_MAX_LENGTH];
task_title[0] = '\0';
/* Set final task title */
task_free_title(task);
if (pfd_install_handle->success)
msg_str = pfd_install_handle->core_already_installed ?
msg_hash_to_str(MSG_LATEST_CORE_INSTALLED) :
msg_hash_to_str(MSG_CORE_INSTALLED);
strlcpy(task_title, msg_str, sizeof(task_title));
strlcat(task_title, pfd_install_handle->display_name,
sizeof(task_title));
task_set_title(task, strdup(task_title));
}
/* fall-through */
default:
task_set_progress(task, 100);
goto task_finished;
}
return;
task_finished:
if (task)
task_set_finished(task, true);
free_play_feature_delivery_install_handle(pfd_install_handle);
}
static bool task_play_feature_delivery_core_install_finder(
retro_task_t *task, void *user_data)
{
if (!task)
return false;
if (task->handler == task_play_feature_delivery_core_install_handler)
return true;
return false;
}
void task_push_play_feature_delivery_core_install(
core_updater_list_t* core_list,
const char *filename)
{
task_finder_data_t find_data;
char task_title[PATH_MAX_LENGTH];
const core_updater_list_entry_t *list_entry = NULL;
retro_task_t *task = NULL;
play_feature_delivery_install_handle_t *pfd_install_handle = (play_feature_delivery_install_handle_t*)
calloc(1, sizeof(play_feature_delivery_install_handle_t));
task_title[0] = '\0';
/* Sanity check */
if (!core_list ||
string_is_empty(filename) ||
!pfd_install_handle ||
!play_feature_delivery_enabled())
goto error;
/* Get core updater list entry */
if (!core_updater_list_get_filename(
core_list, filename, &list_entry))
goto error;
if (string_is_empty(list_entry->local_core_path) ||
string_is_empty(list_entry->display_name))
goto error;
/* Only one core may be downloaded at a time */
find_data.func = task_play_feature_delivery_core_install_finder;
find_data.userdata = NULL;
if (task_queue_find(&find_data))
goto error;
/* Configure handle */
pfd_install_handle->core_filename = strdup(list_entry->remote_filename);
pfd_install_handle->local_core_path = strdup(list_entry->local_core_path);
pfd_install_handle->display_name = strdup(list_entry->display_name);
pfd_install_handle->success = false;
pfd_install_handle->core_already_installed = false;
pfd_install_handle->status = PLAY_FEATURE_DELIVERY_INSTALL_BEGIN;
/* Create task */
task = task_init();
if (!task)
goto error;
/* Configure task */
strlcpy(task_title, msg_hash_to_str(MSG_UPDATING_CORE),
sizeof(task_title));
strlcat(task_title, pfd_install_handle->display_name,
sizeof(task_title));
task->handler = task_play_feature_delivery_core_install_handler;
task->state = pfd_install_handle;
task->mute = false;
task->title = strdup(task_title);
task->alternative_look = true;
task->progress = 0;
task->callback = cb_task_core_updater_download;
/* Install process may involve the *deletion*
* of an existing core file. If core is
* already running, must therefore unload it
* to prevent undefined behaviour */
if (rarch_ctl(RARCH_CTL_IS_CORE_LOADED, (void*)list_entry->local_core_path))
command_event(CMD_EVENT_UNLOAD_CORE, NULL);
/* Push task */
task_queue_push(task);
return;
error:
/* Clean up task */
if (task)
{
free(task);
task = NULL;
}
/* Clean up handle */
free_play_feature_delivery_install_handle(pfd_install_handle);
}
#endif

View File

@ -96,6 +96,11 @@ void task_push_update_installed_cores(
bool auto_backup, size_t auto_backup_history_size, bool auto_backup, size_t auto_backup_history_size,
const char *path_dir_libretro, const char *path_dir_libretro,
const char *path_dir_core_assets); const char *path_dir_core_assets);
#if defined(ANDROID)
void task_push_play_feature_delivery_core_install(
core_updater_list_t* core_list,
const char *filename);
#endif
bool task_push_pl_entry_thumbnail_download( bool task_push_pl_entry_thumbnail_download(
const char *system, const char *system,