diff --git a/Makefile.common b/Makefile.common index 13b3d4e24d..2e73a458d8 100644 --- a/Makefile.common +++ b/Makefile.common @@ -2212,6 +2212,15 @@ ifeq ($(HAVE_VITAGL), 1) OBJ += $(patsubst %.c,%.o,$(foreach dir,$(SOURCES), $(wildcard $(dir)/*.c))) 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 ### ###############WIP################ diff --git a/core_info.c b/core_info.c index 20fa4c2884..76cd77b5aa 100644 --- a/core_info.c +++ b/core_info.c @@ -36,6 +36,10 @@ #include "uwp/uwp_func.h" #endif +#if defined(ANDROID) +#include "play_feature_delivery/play_feature_delivery.h" +#endif + enum compare_op { 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'; +#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)) return false; @@ -1599,6 +1610,13 @@ bool core_info_get_core_lock(const char *core_path, bool validate_path) 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)) return false; diff --git a/core_updater_list.c b/core_updater_list.c index 0a39401f21..80c8b32aea 100644 --- a/core_updater_list.c +++ b/core_updater_list.c @@ -36,6 +36,7 @@ struct core_updater_list { core_updater_list_entry_t *entries; + enum core_updater_list_type type; }; /* Cached ('global') core updater list */ @@ -109,6 +110,7 @@ core_updater_list_t *core_updater_list_init(void) /* Initialise members */ core_list->entries = NULL; + core_list->type = CORE_UPDATER_LIST_TYPE_UNKNOWN; return core_list; } @@ -129,6 +131,8 @@ void core_updater_list_reset(core_updater_list_t *core_list) RBUF_FREE(core_list->entries); } + + core_list->type = CORE_UPDATER_LIST_TYPE_UNKNOWN; } /* 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); } +/* 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 * to the specified entry index. * 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_libretro_info, 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 *tmp_url = NULL; @@ -384,11 +400,14 @@ static bool core_updater_list_set_paths( local_core_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; - if (string_is_empty(path_dir_libretro) || - string_is_empty(path_libretro_info) || + /* Only buildbot cores require the buildbot URL */ + if ((list_type == CORE_UPDATER_LIST_TYPE_BUILDBOT) && string_is_empty(network_buildbot_url)) return false; @@ -404,20 +423,24 @@ static bool core_updater_list_set_paths( entry->remote_filename = strdup(filename_str); - /* remote_core_path */ - fill_pathname_join( - remote_core_path, - network_buildbot_url, - filename_str, - sizeof(remote_core_path)); + /* remote_core_path + * > Leave blank if this is not a buildbot core */ + if (list_type == CORE_UPDATER_LIST_TYPE_BUILDBOT) + { + fill_pathname_join( + remote_core_path, + network_buildbot_url, + filename_str, + sizeof(remote_core_path)); - /* > Apply proper URL encoding (messy...) */ - tmp_url = strdup(remote_core_path); - remote_core_path[0] = '\0'; - net_http_urlencode_full( - remote_core_path, tmp_url, sizeof(remote_core_path)); - if (tmp_url) - free(tmp_url); + /* > Apply proper URL encoding (messy...) */ + tmp_url = strdup(remote_core_path); + remote_core_path[0] = '\0'; + net_http_urlencode_full( + remote_core_path, tmp_url, sizeof(remote_core_path)); + if (tmp_url) + free(tmp_url); + } if (entry->remote_core_path) { @@ -677,7 +700,8 @@ static void core_updater_list_add_entry( path_dir_libretro, path_libretro_info, network_buildbot_url, - filename_str)) + filename_str, + CORE_UPDATER_LIST_TYPE_BUILDBOT)) goto error; if (!core_updater_list_set_core_info( @@ -756,8 +780,8 @@ bool core_updater_list_parse_network_data( const char *data, size_t len) { size_t i; - char *data_buf = NULL; - struct string_list network_core_list = {0}; + char *data_buf = NULL; + struct string_list network_core_list = {0}; /* Sanity check */ if (!core_list || string_is_empty(data) || (len < 1)) @@ -825,6 +849,9 @@ bool core_updater_list_parse_network_data( /* Sort completed list */ core_updater_list_qsort(core_list); + /* Set list type */ + core_list->type = CORE_UPDATER_LIST_TYPE_BUILDBOT; + return true; error: @@ -835,3 +862,114 @@ error: 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; +} diff --git a/core_updater_list.h b/core_updater_list.h index b1f314f42e..79857b236d 100644 --- a/core_updater_list.h +++ b/core_updater_list.h @@ -32,6 +32,18 @@ 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 * on the buildbot */ typedef struct @@ -99,6 +111,11 @@ void core_updater_list_free_cached(void); /* Returns number of entries in core updater 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 * to the specified entry index. * Returns false if index is invalid. */ @@ -138,6 +155,16 @@ bool core_updater_list_parse_network_data( const char *network_buildbot_url, 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 #endif diff --git a/frontend/drivers/platform_unix.c b/frontend/drivers/platform_unix.c index 05c5bad2cc..1d257bdc66 100644 --- a/frontend/drivers/platform_unix.c +++ b/frontend/drivers/platform_unix.c @@ -1938,6 +1938,16 @@ static void frontend_unix_init(void *data) "doVibrate", "(IIII)V"); GET_METHOD_ID(env, android_app->getUserLanguageString, class, "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, android_app->getIntent); diff --git a/frontend/drivers/platform_unix.h b/frontend/drivers/platform_unix.h index 13d6007b1c..3ac5c4b61c 100644 --- a/frontend/drivers/platform_unix.h +++ b/frontend/drivers/platform_unix.h @@ -166,6 +166,12 @@ struct android_app jmethodID getUserLanguageString; jmethodID doVibrate; + jmethodID isPlayStoreBuild; + jmethodID getAvailableCores; + jmethodID getInstalledCores; + jmethodID downloadCore; + jmethodID deleteCore; + struct { unsigned width, height; diff --git a/griffin/griffin.c b/griffin/griffin.c index c9cae29d5b..cb0a430d8f 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -1671,3 +1671,10 @@ MISC FILE FORMATS TIME ============================================================ */ #include "../libretro-common/time/rtime.c" + +/*============================================================ +ANDROID PLAY FEATURE DELIVERY +============================================================ */ +#if defined(ANDROID) +#include "../play_feature_delivery/play_feature_delivery.c" +#endif diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index ef81644526..491cf8bf58 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -10084,6 +10084,10 @@ MSG_HASH( MSG_CORE_INSTALLED, "Core installed: " ) +MSG_HASH( + MSG_CORE_INSTALL_FAILED, + "Failed to install core: " + ) MSG_HASH( MSG_SCANNING_CORES, "Scanning cores..." diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index 2087499532..88669c7456 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -89,6 +89,10 @@ #include "../../uwp/uwp_func.h" #endif +#if defined(ANDROID) +#include "../../play_feature_delivery/play_feature_delivery.h" +#endif + enum { 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) { 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 * it if required */ @@ -4036,12 +4042,49 @@ static int action_ok_core_updater_list(const char *path, return menu_cbs_exit(); } - /* Initial setup... */ - menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh); - generic_action_ok_command(CMD_EVENT_NETWORK_INIT); +#if defined(ANDROID) + if (play_feature_delivery_enabled()) + { + /* 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 */ - task_push_get_core_updater_list(core_list, false, true); + if (!available_cores) + 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( path, NULL, label, type, idx, entry_idx, @@ -4466,10 +4509,18 @@ static int action_ok_core_updater_download(const char *path, if (!core_list) return menu_cbs_exit(); - 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); +#if defined(ANDROID) + /* Play Store builds install cores via + * the play feature delivery interface */ + 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 return 0; @@ -6677,7 +6728,24 @@ static int action_ok_core_delete(const char *path, generic_action_ok_command(CMD_EVENT_UNLOAD_CORE); /* 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 */ command_event(CMD_EVENT_CORE_INFO_INIT, NULL); diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 4b88bd3490..fb3da9dee2 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -62,6 +62,10 @@ #include "../frontend/drivers/platform_unix.h" #endif +#if defined(ANDROID) +#include "../play_feature_delivery/play_feature_delivery.h" +#endif + #ifdef HAVE_CDROM #include #include @@ -670,21 +674,28 @@ end: /* Check whether core is currently locked */ bool core_locked = core_info_get_core_lock(core_path, true); - /* Lock core - * > Note: Have to set core_path as both the - * '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)) +#if defined(ANDROID) + /* Play Store builds do not support + * core locking */ + if (!play_feature_delivery_enabled()) +#endif { - file_list_set_alt_at_offset( - info->list, count, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_LOCK)); - count++; + /* Lock core + * > Note: Have to set core_path as both the + * '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 */ @@ -11054,12 +11065,18 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, MENU_SETTING_ACTION, 0, 0)) count++; - 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++; +#if defined(ANDROID) + /* Play Store builds auto-update installed + * cores, rendering the 'update installed + * cores' option irrelevant/useless */ + if (!play_feature_delivery_enabled()) +#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 diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 5cf40c3c91..880c23e137 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -118,6 +118,10 @@ #include <3ds/services/cfgu.h> #endif +#if defined(ANDROID) +#include "../play_feature_delivery/play_feature_delivery.h" +#endif + #define _3_SECONDS 3000000 #define _6_SECONDS 6000000 #define _9_SECONDS 9000000 @@ -16682,21 +16686,29 @@ static bool setting_append_list( parent_group = msg_hash_to_str(MENU_ENUM_LABEL_UPDATER_SETTINGS); START_SUB_GROUP(list, list_info, "State", &group_info, &subgroup_info, parent_group); #ifdef HAVE_NETWORKING - CONFIG_STRING( - list, list_info, - settings->paths.network_buildbot_url, - sizeof(settings->paths.network_buildbot_url), - MENU_ENUM_LABEL_CORE_UPDATER_BUILDBOT_URL, - MENU_ENUM_LABEL_VALUE_CORE_UPDATER_BUILDBOT_URL, - DEFAULT_BUILDBOT_SERVER_URL, - &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; + +#if defined(ANDROID) + /* Play Store builds do not fetch cores + * from the buildbot */ + if (!play_feature_delivery_enabled()) +#endif + { + CONFIG_STRING( + list, list_info, + settings->paths.network_buildbot_url, + sizeof(settings->paths.network_buildbot_url), + MENU_ENUM_LABEL_CORE_UPDATER_BUILDBOT_URL, + MENU_ENUM_LABEL_VALUE_CORE_UPDATER_BUILDBOT_URL, + DEFAULT_BUILDBOT_SERVER_URL, + &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( list, list_info, @@ -16746,37 +16758,44 @@ static bool setting_append_list( SD_FLAG_NONE ); - CONFIG_BOOL( - list, list_info, - &settings->bools.core_updater_auto_backup, - MENU_ENUM_LABEL_CORE_UPDATER_AUTO_BACKUP, - MENU_ENUM_LABEL_VALUE_CORE_UPDATER_AUTO_BACKUP, - DEFAULT_CORE_UPDATER_AUTO_BACKUP, - MENU_ENUM_LABEL_VALUE_OFF, - MENU_ENUM_LABEL_VALUE_ON, - &group_info, - &subgroup_info, - parent_group, - general_write_handler, - general_read_handler, - SD_FLAG_NONE - ); - - CONFIG_UINT( +#if defined(ANDROID) + /* Play Store builds do not support automatic + * core backups */ + if (!play_feature_delivery_enabled()) +#endif + { + CONFIG_BOOL( 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, + &settings->bools.core_updater_auto_backup, + MENU_ENUM_LABEL_CORE_UPDATER_AUTO_BACKUP, + MENU_ENUM_LABEL_VALUE_CORE_UPDATER_AUTO_BACKUP, + DEFAULT_CORE_UPDATER_AUTO_BACKUP, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, &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); + general_read_handler, + SD_FLAG_NONE + ); + + 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 END_SUB_GROUP(list, list_info, parent_group); END_GROUP(list, list_info, parent_group); diff --git a/msg_hash.h b/msg_hash.h index d352ebe0ae..9cec4652b1 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -2073,6 +2073,7 @@ enum msg_hash_enums MSG_DOWNLOADING_CORE, MSG_EXTRACTING_CORE, MSG_CORE_INSTALLED, + MSG_CORE_INSTALL_FAILED, MSG_SCANNING_CORES, MSG_CHECKING_CORE, MSG_ALL_CORES_UPDATED, diff --git a/play_feature_delivery/com_retroarch_browser_retroactivity_RetroActivityCommon.h b/play_feature_delivery/com_retroarch_browser_retroactivity_RetroActivityCommon.h new file mode 100644 index 0000000000..e1e87e2759 --- /dev/null +++ b/play_feature_delivery/com_retroarch_browser_retroactivity_RetroActivityCommon.h @@ -0,0 +1,89 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* 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 diff --git a/play_feature_delivery/play_feature_delivery.c b/play_feature_delivery/play_feature_delivery.c new file mode 100644 index 0000000000..357c2dc37b --- /dev/null +++ b/play_feature_delivery/play_feature_delivery.c @@ -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 . + */ + +#include "com_retroarch_browser_retroactivity_RetroActivityCommon.h" + +#ifdef HAVE_THREADS +#include +#include +#include +#endif + +#include +#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; +} diff --git a/play_feature_delivery/play_feature_delivery.h b/play_feature_delivery/play_feature_delivery.h new file mode 100644 index 0000000000..d27a2b71f7 --- /dev/null +++ b/play_feature_delivery/play_feature_delivery.h @@ -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 . + */ + +#ifndef __PLAY_FEATURE_DELIVERY_H +#define __PLAY_FEATURE_DELIVERY_H + +#include +#include + +#include + +#include + +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 diff --git a/retroarch.c b/retroarch.c index b528e9e15e..606562a4c0 100644 --- a/retroarch.c +++ b/retroarch.c @@ -120,6 +120,10 @@ #include "switch_performance_profiles.h" #endif +#if defined(ANDROID) +#include "play_feature_delivery/play_feature_delivery.h" +#endif + #ifdef HAVE_DISCORD #include #include "deps/discord-rpc/include/discord_rpc.h" @@ -17539,6 +17543,10 @@ void main_exit(void *args) rtime_deinit(); +#if defined(ANDROID) + play_feature_delivery_deinit(); +#endif + #if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__) CoUninitialize(); #endif @@ -17577,6 +17585,10 @@ int rarch_main(int argc, char *argv[], void *data) rtime_init(); +#if defined(ANDROID) + play_feature_delivery_init(); +#endif + libretro_free_system_info(&p_rarch->runloop_system.info); command_event(CMD_EVENT_HISTORY_DEINIT, NULL); rarch_favorites_deinit(); diff --git a/tasks/task_core_backup.c b/tasks/task_core_backup.c index d9f67527db..5f04cbbe77 100644 --- a/tasks/task_core_backup.c +++ b/tasks/task_core_backup.c @@ -36,6 +36,10 @@ #include "../core_info.h" #include "../core_backup.h" +#if defined(ANDROID) +#include "../play_feature_delivery/play_feature_delivery.h" +#endif + #define CORE_BACKUP_CHUNK_SIZE 4096 enum core_backup_status @@ -778,6 +782,27 @@ static void task_core_restore_handler(retro_task_t *task) 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 */ backup_handle->core_file = intfstream_open_file( backup_handle->core_path, RETRO_VFS_FILE_ACCESS_WRITE, diff --git a/tasks/task_core_updater.c b/tasks/task_core_updater.c index 4552501656..a0171d5f55 100644 --- a/tasks/task_core_updater.c +++ b/tasks/task_core_updater.c @@ -38,6 +38,10 @@ #include "../verbosity.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) #include "../menu/menu_entries.h" #endif @@ -50,29 +54,6 @@ enum core_updater_list_status 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 { core_updater_list_t* core_list; @@ -85,6 +66,18 @@ typedef struct core_updater_list_handle bool http_task_success; } 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 { char *path_dir_libretro; @@ -110,6 +103,17 @@ typedef struct core_updater_download_handle bool backup_enabled; } 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 { char *path_dir_libretro; @@ -127,6 +131,26 @@ typedef struct update_installed_cores_handle bool auto_backup; } 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 */ /*********************/ @@ -393,6 +417,13 @@ void *task_push_get_core_updater_list( core_updater_list_handle_t *list_handle = (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 */ if (!core_list || !list_handle) goto error; @@ -952,6 +983,13 @@ void *task_push_core_updater_download( task_title[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 */ if (!core_list || string_is_empty(filename) || @@ -1410,6 +1448,13 @@ void task_push_update_installed_cores( (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 */ if (!update_installed_handle || string_is_empty(path_dir_libretro)) @@ -1471,3 +1516,265 @@ error: /* Clean up 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 diff --git a/tasks/tasks_internal.h b/tasks/tasks_internal.h index ba9af8dad0..bc44eacaf4 100644 --- a/tasks/tasks_internal.h +++ b/tasks/tasks_internal.h @@ -96,6 +96,11 @@ void task_push_update_installed_cores( bool auto_backup, size_t auto_backup_history_size, const char *path_dir_libretro, 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( const char *system,