/* 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 - 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 #include #include #include #include #include #include #include #include #include #include "task_file_transfer.h" #include "tasks_internal.h" #include "../configuration.h" #include "../retroarch.h" #include "../command.h" #include "../msg_hash.h" #include "../verbosity.h" #include "../core_updater_list.h" #if defined(ANDROID) #include "../file_path_special.h" #include "../play_feature_delivery/play_feature_delivery.h" #endif #if defined(RARCH_INTERNAL) && defined(HAVE_MENU) #include "../menu/menu_entries.h" #include "../menu/menu_driver.h" #endif /* Get core updater list */ enum core_updater_list_status { CORE_UPDATER_LIST_BEGIN = 0, CORE_UPDATER_LIST_WAIT, CORE_UPDATER_LIST_END }; typedef struct core_updater_list_handle { core_updater_list_t* core_list; retro_task_t *http_task; http_transfer_data_t *http_data; enum core_updater_list_status status; bool refresh_menu; bool http_task_finished; bool http_task_complete; 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; char *path_dir_core_assets; char *remote_filename; char *remote_core_path; char *local_download_path; char *local_core_path; char *display_name; retro_task_t *http_task; retro_task_t *decompress_task; retro_task_t *backup_task; size_t auto_backup_history_size; uint32_t local_crc; uint32_t remote_crc; enum core_updater_download_status status; bool crc_match; bool http_task_finished; bool http_task_complete; bool auto_backup; bool decompress_task_finished; bool decompress_task_complete; 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; char *path_dir_core_assets; core_updater_list_t* core_list; retro_task_t *list_task; retro_task_t *download_task; size_t auto_backup_history_size; size_t list_size; size_t list_index; size_t installed_index; unsigned num_updated; unsigned num_locked; enum update_installed_cores_status status; bool auto_backup; } update_installed_cores_handle_t; enum update_single_core_status { UPDATE_SINGLE_CORE_BEGIN = 0, UPDATE_SINGLE_CORE_WAIT_LIST, UPDATE_SINGLE_CORE_UPDATE_CORE, UPDATE_SINGLE_CORE_WAIT_DOWNLOAD, UPDATE_SINGLE_CORE_END }; typedef struct update_single_core_handle { core_updater_list_t *core_list; size_t auto_backup_history_size; enum update_single_core_status status; char path_core[PATH_MAX_LENGTH]; char path_dir_libretro[PATH_MAX_LENGTH]; char path_dir_core_assets[PATH_MAX_LENGTH]; bool auto_backup; } update_single_core_handle_t; #if defined(ANDROID) /* Play feature delivery core install */ 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 *backup_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; /* Play feature delivery switch installed cores */ enum play_feature_delivery_switch_cores_task_status { PLAY_FEATURE_DELIVERY_SWITCH_CORES_BEGIN = 0, PLAY_FEATURE_DELIVERY_SWITCH_CORES_ITERATE, PLAY_FEATURE_DELIVERY_SWITCH_CORES_INSTALL_CORE, PLAY_FEATURE_DELIVERY_SWITCH_CORES_WAIT_INSTALL, PLAY_FEATURE_DELIVERY_SWITCH_CORES_END }; typedef struct play_feature_delivery_switch_cores_handle { char *path_dir_libretro; char *path_libretro_info; char *error_msg; core_updater_list_t* core_list; retro_task_t *install_task; size_t list_size; size_t list_index; size_t installed_index; enum play_feature_delivery_switch_cores_task_status status; } play_feature_delivery_switch_cores_handle_t; #endif /*********************/ /* Utility functions */ /*********************/ /* Returns CRC32 of specified core file */ static uint32_t task_core_updater_get_core_crc(const char *core_path) { /* Open core file */ intfstream_t *core_file = intfstream_open_file( core_path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); if (core_file) { uint32_t crc = 0; /* Get CRC value */ bool success = intfstream_get_crc(core_file, &crc); /* Close core file */ intfstream_close(core_file); free(core_file); core_file = NULL; if (success) return crc; } return 0; } /*************************/ /* Get core updater list */ /*************************/ static void cb_http_task_core_updater_get_list( retro_task_t *task, void *task_data, void *user_data, const char *err) { http_transfer_data_t *data = (http_transfer_data_t*)task_data; file_transfer_t *transf = (file_transfer_t*)user_data; core_updater_list_handle_t *list_handle = NULL; bool success = data && string_is_empty(err); if (!transf) goto finish; if (!(list_handle = (core_updater_list_handle_t*)transf->user_data)) goto finish; task_set_data(task, NULL); /* going to pass ownership to list_handle */ list_handle->http_data = data; list_handle->http_task_complete = true; list_handle->http_task_success = success; finish: /* Log any error messages */ if (!success) RARCH_ERR("[core updater] Download of core list '%s' failed: %s\n", (transf ? transf->path: "unknown"), (err ? err : "unknown")); if (transf) free(transf); } static void free_core_updater_list_handle( core_updater_list_handle_t *list_handle) { if (list_handle->http_data) { /* since we took onwership, we have to destroy it ourself */ if (list_handle->http_data->data) free(list_handle->http_data->data); free(list_handle->http_data); } free(list_handle); list_handle = NULL; } static void task_core_updater_get_list_handler(retro_task_t *task) { core_updater_list_handle_t *list_handle = NULL; if (!task) goto task_finished; if (!(list_handle = (core_updater_list_handle_t*)task->state)) goto task_finished; if (task_get_cancelled(task)) goto task_finished; switch (list_handle->status) { case CORE_UPDATER_LIST_BEGIN: { char buildbot_url[PATH_MAX_LENGTH]; settings_t *settings = config_get_ptr(); file_transfer_t *transf = NULL; char *tmp_url = NULL; const char *net_buildbot_url = settings->paths.network_buildbot_url; /* Reset core updater list */ core_updater_list_reset(list_handle->core_list); /* Get core listing URL */ if (!settings) goto task_finished; if (string_is_empty(net_buildbot_url)) goto task_finished; fill_pathname_join_special( buildbot_url, net_buildbot_url, ".index-extended", sizeof(buildbot_url)); tmp_url = strdup(buildbot_url); buildbot_url[0] = '\0'; net_http_urlencode_full( buildbot_url, tmp_url, sizeof(buildbot_url)); if (tmp_url) free(tmp_url); if (string_is_empty(buildbot_url)) goto task_finished; /* Configure file transfer object */ if (!(transf = (file_transfer_t*)calloc(1, sizeof(file_transfer_t)))) goto task_finished; /* > Seems to be required - not sure why the * underlying code is implemented like this... */ strlcpy(transf->path, buildbot_url, sizeof(transf->path)); transf->user_data = (void*)list_handle; /* Push HTTP transfer task */ list_handle->http_task = (retro_task_t*) task_push_http_transfer_file( buildbot_url, true, NULL, cb_http_task_core_updater_get_list, transf); /* Start waiting for HTTP transfer to complete */ list_handle->status = CORE_UPDATER_LIST_WAIT; } break; case CORE_UPDATER_LIST_WAIT: { /* If HTTP task is NULL, then it either finished * or an error occurred - in either case, * just move on to the next state */ if (!list_handle->http_task) list_handle->http_task_complete = true; /* Otherwise, check if HTTP task is still running */ else if (!list_handle->http_task_finished) { /* If HTTP task is running, copy current * progress value to *this* task */ if (!(list_handle->http_task_finished = task_get_finished(list_handle->http_task))) task_set_progress( task, task_get_progress(list_handle->http_task)); } /* Wait for task_push_http_transfer_file() * callback to trigger */ if (list_handle->http_task_complete) list_handle->status = CORE_UPDATER_LIST_END; } break; case CORE_UPDATER_LIST_END: { settings_t *settings = config_get_ptr(); /* Check whether HTTP task was successful */ if (list_handle->http_task_success) { /* Parse HTTP transfer data */ if (list_handle->http_data) core_updater_list_parse_network_data( list_handle->core_list, settings->paths.directory_libretro, settings->paths.path_libretro_info, settings->paths.network_buildbot_url, list_handle->http_data->data, list_handle->http_data->len); } else { /* Notify user of error via task title */ task_free_title(task); task_set_title(task, strdup(msg_hash_to_str(MSG_CORE_LIST_FAILED))); } /* Enable menu refresh, if required */ #if defined(RARCH_INTERNAL) && defined(HAVE_MENU) if (list_handle->refresh_menu) menu_entries_ctl( MENU_ENTRIES_CTL_UNSET_REFRESH, &list_handle->refresh_menu); #endif } /* fall-through */ default: task_set_progress(task, 100); goto task_finished; } return; task_finished: if (task) task_set_finished(task, true); if (list_handle) free_core_updater_list_handle(list_handle); } static bool task_core_updater_get_list_finder(retro_task_t *task, void *user_data) { core_updater_list_handle_t *list_handle = NULL; if (!task || !user_data) return false; if (task->handler != task_core_updater_get_list_handler) return false; if (!(list_handle = (core_updater_list_handle_t*)task->state)) return false; return ((uintptr_t)user_data == (uintptr_t)list_handle->core_list); } void *task_push_get_core_updater_list( core_updater_list_t* core_list, bool mute, bool refresh_menu) { task_finder_data_t find_data; retro_task_t *task = NULL; 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; /* Configure handle */ list_handle->core_list = core_list; list_handle->refresh_menu = refresh_menu; list_handle->http_task = NULL; list_handle->http_task_finished = false; list_handle->http_task_complete = false; list_handle->http_task_success = false; list_handle->http_data = NULL; list_handle->status = CORE_UPDATER_LIST_BEGIN; /* Concurrent downloads of the buildbot core listing * to the same core_updater_list_t object are not * allowed */ find_data.func = task_core_updater_get_list_finder; find_data.userdata = (void*)core_list; if (task_queue_find(&find_data)) goto error; /* Create task */ if (!(task = task_init())) goto error; /* Configure task */ task->handler = task_core_updater_get_list_handler; task->state = list_handle; task->mute = mute; task->title = strdup(msg_hash_to_str(MSG_FETCHING_CORE_LIST)); task->alternative_look = true; task->progress = 0; /* Push task */ task_queue_push(task); return task; error: /* Clean up task */ if (task) { free(task); task = NULL; } /* Clean up handle */ if (list_handle) free_core_updater_list_handle(list_handle); return NULL; } /*****************/ /* Download core */ /*****************/ static void cb_task_core_updater_download( retro_task_t *task, void *task_data, void *user_data, const char *err) { /* Reload core info files * > This must be done on the main thread */ command_event(CMD_EVENT_CORE_INFO_INIT, NULL); #if defined(RARCH_INTERNAL) && defined(HAVE_MENU) /* Force reload of contentless cores icons */ menu_contentless_cores_free(); #endif } static void cb_decompress_task_core_updater_download( retro_task_t *task, void *task_data, void *user_data, const char *err) { decompress_task_data_t *decompress_data = (decompress_task_data_t*)task_data; core_updater_download_handle_t *download_handle = (core_updater_download_handle_t*)user_data; /* Signal that decompression task is complete */ if (download_handle) download_handle->decompress_task_complete = true; /* Remove original archive file */ if (decompress_data) { if (!string_is_empty(decompress_data->source_file)) if (path_is_valid(decompress_data->source_file)) filestream_delete(decompress_data->source_file); if (decompress_data->source_file) free(decompress_data->source_file); free(decompress_data); } /* Log any error messages */ if (!string_is_empty(err)) RARCH_ERR("[core updater] %s", err); } void cb_http_task_core_updater_download( retro_task_t *task, void *task_data, void *user_data, const char *err) { http_transfer_data_t *data = (http_transfer_data_t*)task_data; file_transfer_t *transf = (file_transfer_t*)user_data; core_updater_download_handle_t *download_handle = NULL; char output_dir[PATH_MAX_LENGTH]; if (!data || !transf) goto finish; if (!data->data || string_is_empty(transf->path)) goto finish; if (!(download_handle = (core_updater_download_handle_t*)transf->user_data)) goto finish; /* Update download_handle task status */ download_handle->http_task_complete = true; /* Create output directory, if required */ strlcpy(output_dir, transf->path, sizeof(output_dir)); path_basedir_wrapper(output_dir); if (!path_mkdir(output_dir)) { err = msg_hash_to_str(MSG_FAILED_TO_CREATE_THE_DIRECTORY); goto finish; } #ifdef HAVE_COMPRESSION /* If core file is an archive, make sure it is * not being decompressed already (by another task) */ if (path_is_compressed_file(transf->path)) { if (task_check_decompress(transf->path)) { err = msg_hash_to_str(MSG_DECOMPRESSION_ALREADY_IN_PROGRESS); goto finish; } } #endif /* Write core file to disk */ if (!filestream_write_file(transf->path, data->data, data->len)) { err = "Write failed."; goto finish; } #if defined(HAVE_COMPRESSION) && defined(HAVE_ZLIB) /* Decompress core file, if required * NOTE: If core is compressed and platform * doesn't have compression support, then this * whole thing falls apart... * We assume that the build process is configured * in such a way that this cannot happen... */ if (path_is_compressed_file(transf->path)) { if (!(download_handle->decompress_task = (retro_task_t*)task_push_decompress( transf->path, output_dir, NULL, NULL, NULL, cb_decompress_task_core_updater_download, (void*)download_handle, NULL, true))) { err = msg_hash_to_str(MSG_DECOMPRESSION_FAILED); goto finish; } } #endif finish: /* Log any error messages */ if (!string_is_empty(err)) RARCH_ERR("[core updater] Download of '%s' failed: %s\n", (transf ? transf->path: "unknown"), err); if (transf) free(transf); /* if no decompress task was queued, mark it as completed */ if (!download_handle->decompress_task) download_handle->decompress_task_complete = true; } static void free_core_updater_download_handle(core_updater_download_handle_t *download_handle) { if (download_handle->path_dir_libretro) free(download_handle->path_dir_libretro); if (download_handle->path_dir_core_assets) free(download_handle->path_dir_core_assets); if (download_handle->remote_filename) free(download_handle->remote_filename); if (download_handle->remote_core_path) free(download_handle->remote_core_path); if (download_handle->local_download_path) free(download_handle->local_download_path); if (download_handle->local_core_path) free(download_handle->local_core_path); if (download_handle->display_name) free(download_handle->display_name); free(download_handle); download_handle = NULL; } static void task_core_updater_download_handler(retro_task_t *task) { core_updater_download_handle_t *download_handle = NULL; if (!task) goto task_finished; if (!(download_handle = (core_updater_download_handle_t*)task->state)) goto task_finished; if (task_get_cancelled(task)) goto task_finished; switch (download_handle->status) { case CORE_UPDATER_DOWNLOAD_BEGIN: { /* Get CRC of existing core, if required */ if (download_handle->local_crc == 0) { const char *local_core_path = download_handle->local_core_path; if ( !string_is_empty(local_core_path) && path_is_valid (local_core_path) ) download_handle->local_crc = task_core_updater_get_core_crc(local_core_path); } /* Check whether existing core and remote core * have the same CRC */ download_handle->crc_match = (download_handle->local_crc != 0) && (download_handle->local_crc == download_handle->remote_crc); /* If CRC matches, end task immediately */ if (download_handle->crc_match) { download_handle->status = CORE_UPDATER_DOWNLOAD_END; break; } /* If automatic backups are enabled and core is * already installed, trigger a backup - otherwise, * initialise download */ download_handle->backup_enabled = download_handle->auto_backup && path_is_valid(download_handle->local_core_path); download_handle->status = download_handle->backup_enabled ? CORE_UPDATER_DOWNLOAD_START_BACKUP : CORE_UPDATER_DOWNLOAD_START_TRANSFER; } break; case CORE_UPDATER_DOWNLOAD_START_BACKUP: { /* Request core backup */ download_handle->backup_task = (retro_task_t*)task_push_core_backup( download_handle->local_core_path, download_handle->display_name, download_handle->local_crc, CORE_BACKUP_MODE_AUTO, download_handle->auto_backup_history_size, download_handle->path_dir_core_assets, true); if (download_handle->backup_task) { char task_title[PATH_MAX_LENGTH]; /* Update task title */ task_free_title(task); strlcpy( task_title, msg_hash_to_str(MSG_BACKING_UP_CORE), sizeof(task_title)); strlcat(task_title, download_handle->display_name, sizeof(task_title)); task_set_title(task, strdup(task_title)); /* Start waiting for backup to complete */ download_handle->status = CORE_UPDATER_DOWNLOAD_WAIT_BACKUP; } else { /* This cannot realistically happen... * > If it does, just log an error and initialise * download */ RARCH_ERR("[core updater] Failed to backup core: %s\n", download_handle->local_core_path); download_handle->backup_enabled = false; download_handle->status = CORE_UPDATER_DOWNLOAD_START_TRANSFER; } } break; case CORE_UPDATER_DOWNLOAD_WAIT_BACKUP: { bool backup_complete = false; /* > If task is running, check 'is finished' * status * > If task is NULL, then it is finished * by definition */ if (download_handle->backup_task) { backup_complete = task_get_finished(download_handle->backup_task); /* If backup task is running, copy current * progress value to *this* task */ if (!backup_complete) { /* Backup accounts for first third of * task progress */ int8_t progress = task_get_progress(download_handle->backup_task); task_set_progress(task, (int8_t)(((float)progress * (1.0f / 3.0f)) + 0.5f)); } } else backup_complete = true; /* If backup is complete, initialise download */ if (backup_complete) { download_handle->backup_task = NULL; download_handle->status = CORE_UPDATER_DOWNLOAD_START_TRANSFER; } } break; case CORE_UPDATER_DOWNLOAD_START_TRANSFER: { file_transfer_t *transf = NULL; char task_title[PATH_MAX_LENGTH]; /* Configure file transfer object */ if (!(transf = (file_transfer_t*)calloc(1, sizeof(file_transfer_t)))) goto task_finished; strlcpy( transf->path, download_handle->local_download_path, sizeof(transf->path)); transf->user_data = (void*)download_handle; /* Push HTTP transfer task */ download_handle->http_task = (retro_task_t*)task_push_http_transfer_file( download_handle->remote_core_path, true, NULL, cb_http_task_core_updater_download, transf); /* Update task title */ task_free_title(task); strlcpy( task_title, msg_hash_to_str(MSG_DOWNLOADING_CORE), sizeof(task_title)); strlcat(task_title, download_handle->display_name, sizeof(task_title)); task_set_title(task, strdup(task_title)); /* Start waiting for HTTP transfer to complete */ download_handle->status = CORE_UPDATER_DOWNLOAD_WAIT_TRANSFER; } break; case CORE_UPDATER_DOWNLOAD_WAIT_TRANSFER: { /* If HTTP task is NULL, then it either finished * or an error occurred - in either case, * just move on to the next state */ if (!download_handle->http_task) download_handle->http_task_complete = true; /* Otherwise, check if HTTP task is still running */ else if (!download_handle->http_task_finished) { download_handle->http_task_finished = task_get_finished(download_handle->http_task); /* If HTTP task is running, copy current * progress value to *this* task */ if (!download_handle->http_task_finished) { /* > If backups are enabled, download accounts * for second third of task progress * > Otherwise, download accounts for first half * of task progress */ int8_t progress = task_get_progress(download_handle->http_task); if (download_handle->backup_enabled) progress = (int8_t)(((float)progress * (1.0f / 3.0f)) + (100.0f / 3.0f) + 0.5f); else progress = progress >> 1; task_set_progress(task, progress); } } /* Wait for task_push_http_transfer_file() * callback to trigger */ if (download_handle->http_task_complete) { char task_title[PATH_MAX_LENGTH]; /* Update task title */ task_free_title(task); strlcpy( task_title, msg_hash_to_str(MSG_EXTRACTING_CORE), sizeof(task_title)); strlcat(task_title, download_handle->display_name, sizeof(task_title)); task_set_title(task, strdup(task_title)); /* Start waiting for file to be extracted */ download_handle->status = CORE_UPDATER_DOWNLOAD_WAIT_DECOMPRESS; } } break; case CORE_UPDATER_DOWNLOAD_WAIT_DECOMPRESS: { /* If decompression task is NULL, then it either * hasn't been queued by the download task yet, * or an error occurred. The latter should set * the decompress_task_complete flag and we'll * continue to the next state */ if (download_handle->decompress_task && !download_handle->decompress_task_finished) { download_handle->decompress_task_finished = task_get_finished(download_handle->decompress_task); /* If decompression task is running, copy * current progress value to *this* task */ if (!download_handle->decompress_task_finished) { /* > If backups are enabled, decompression accounts * for last third of task progress * > Otherwise, decompression accounts for second * half of task progress */ int8_t progress = task_get_progress(download_handle->decompress_task); if (download_handle->backup_enabled) progress = (int8_t)(((float)progress * (1.0f / 3.0f)) + (200.0f / 3.0f) + 0.5f); else progress = 50 + (progress >> 1); task_set_progress(task, progress); } } /* Wait for task_push_decompress() * callback to trigger */ if (download_handle->decompress_task_complete) download_handle->status = CORE_UPDATER_DOWNLOAD_END; } break; case CORE_UPDATER_DOWNLOAD_END: { char task_title[PATH_MAX_LENGTH]; /* Set final task title */ task_free_title(task); strlcpy( task_title, download_handle->crc_match ? msg_hash_to_str(MSG_LATEST_CORE_INSTALLED) : msg_hash_to_str(MSG_CORE_INSTALLED), sizeof(task_title)); strlcat(task_title, download_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); if (download_handle) free_core_updater_download_handle(download_handle); } static bool task_core_updater_download_finder(retro_task_t *task, void *user_data) { if (task && user_data && task->handler == task_core_updater_download_handler) { core_updater_download_handle_t *download_handle = NULL; if ((download_handle = (core_updater_download_handle_t*)task->state)) return string_is_equal((const char*)user_data, download_handle->remote_filename); } return false; } void *task_push_core_updater_download( core_updater_list_t* core_list, const char *filename, uint32_t crc, bool mute, bool auto_backup, size_t auto_backup_history_size, const char *path_dir_libretro, const char *path_dir_core_assets) { task_finder_data_t find_data; char task_title[PATH_MAX_LENGTH]; char local_download_path[PATH_MAX_LENGTH]; const core_updater_list_entry_t *list_entry = NULL; retro_task_t *task = NULL; core_updater_download_handle_t *download_handle = (core_updater_download_handle_t*) calloc(1, sizeof(core_updater_download_handle_t)); 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) || !download_handle) 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->remote_core_path) || string_is_empty(list_entry->local_core_path) || string_is_empty(list_entry->display_name)) goto error; /* Check whether core is locked * > Have to set validate_path to 'false' here, * since this may not run on the main thread * > Validation is not required anyway, since core * updater list provides 'sane' core paths */ if (core_info_get_core_lock(list_entry->local_core_path, false)) { RARCH_ERR("[core updater] Update disabled - core is locked: %s\n", list_entry->local_core_path); /* If task is not muted, generate notification */ if (!mute) { char msg[PATH_MAX_LENGTH]; strlcpy(msg, msg_hash_to_str(MSG_CORE_UPDATE_DISABLED), sizeof(msg)); strlcat(msg, list_entry->display_name, sizeof(msg)); runloop_msg_queue_push(msg, 1, 100, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); } goto error; } /* Get local file download path */ if (string_is_empty(path_dir_libretro)) goto error; fill_pathname_join_special( local_download_path, path_dir_libretro, list_entry->remote_filename, sizeof(local_download_path)); /* Configure handle */ download_handle->auto_backup = auto_backup; download_handle->auto_backup_history_size = auto_backup_history_size; download_handle->path_dir_libretro = strdup(path_dir_libretro); download_handle->path_dir_core_assets = string_is_empty(path_dir_core_assets) ? NULL : strdup(path_dir_core_assets); download_handle->remote_filename = strdup(list_entry->remote_filename); download_handle->remote_core_path = strdup(list_entry->remote_core_path); download_handle->local_download_path = strdup(local_download_path); download_handle->local_core_path = strdup(list_entry->local_core_path); download_handle->display_name = strdup(list_entry->display_name); download_handle->local_crc = crc; download_handle->remote_crc = list_entry->crc; download_handle->crc_match = false; download_handle->http_task = NULL; download_handle->http_task_finished = false; download_handle->http_task_complete = false; download_handle->decompress_task = NULL; download_handle->decompress_task_finished = false; download_handle->decompress_task_complete = false; download_handle->backup_enabled = false; download_handle->backup_task = NULL; download_handle->status = CORE_UPDATER_DOWNLOAD_BEGIN; /* Concurrent downloads of the same file are not allowed */ find_data.func = task_core_updater_download_finder; find_data.userdata = (void*)download_handle->remote_filename; if (task_queue_find(&find_data)) goto error; /* Create task */ if (!(task = task_init())) goto error; /* Configure task */ strlcpy( task_title, msg_hash_to_str(MSG_UPDATING_CORE), sizeof(task_title)); strlcat(task_title, download_handle->display_name, sizeof(task_title)); task->handler = task_core_updater_download_handler; task->state = download_handle; task->mute = mute; task->title = strdup(task_title); task->alternative_look = true; task->progress = 0; task->callback = cb_task_core_updater_download; /* Push task */ task_queue_push(task); return task; error: /* Clean up task */ if (task) { free(task); task = NULL; } /* Clean up handle */ if (download_handle) free_core_updater_download_handle(download_handle); return NULL; } /**************************/ /* Update installed cores */ /**************************/ static void free_update_installed_cores_handle( update_installed_cores_handle_t *update_installed_handle) { if (update_installed_handle->path_dir_libretro) free(update_installed_handle->path_dir_libretro); if (update_installed_handle->path_dir_core_assets) free(update_installed_handle->path_dir_core_assets); core_updater_list_free(update_installed_handle->core_list); free(update_installed_handle); update_installed_handle = NULL; } static void task_update_installed_cores_handler(retro_task_t *task) { update_installed_cores_handle_t *update_installed_handle = NULL; if (!task) goto task_finished; update_installed_handle = (update_installed_cores_handle_t*)task->state; if (!update_installed_handle) goto task_finished; if (task_get_cancelled(task)) goto task_finished; switch (update_installed_handle->status) { case UPDATE_INSTALLED_CORES_BEGIN: /* Request buildbot core list */ update_installed_handle->list_task = (retro_task_t*) task_push_get_core_updater_list( update_installed_handle->core_list, true, false); /* If push failed, go to end * (error will message will be displayed when * final task title is set) */ if (!update_installed_handle->list_task) update_installed_handle->status = UPDATE_INSTALLED_CORES_END; else update_installed_handle->status = UPDATE_INSTALLED_CORES_WAIT_LIST; break; case UPDATE_INSTALLED_CORES_WAIT_LIST: { bool list_available = false; /* > If task is running, check 'is finished' * status * > If task is NULL, then it is finished * by definition */ if (update_installed_handle->list_task) list_available = task_get_finished(update_installed_handle->list_task); else list_available = true; /* If list is available, make sure it isn't empty * (error will message will be displayed when * final task title is set) */ if (list_available) { update_installed_handle->list_size = core_updater_list_size(update_installed_handle->core_list); if (update_installed_handle->list_size < 1) update_installed_handle->status = UPDATE_INSTALLED_CORES_END; else update_installed_handle->status = UPDATE_INSTALLED_CORES_ITERATE; } } break; case UPDATE_INSTALLED_CORES_ITERATE: { const core_updater_list_entry_t *list_entry = NULL; bool core_installed = false; /* Check whether we have reached the end * of the list */ if (update_installed_handle->list_index >= update_installed_handle->list_size) { update_installed_handle->status = UPDATE_INSTALLED_CORES_END; break; } /* Check whether current core is installed */ if (core_updater_list_get_index( update_installed_handle->core_list, update_installed_handle->list_index, &list_entry)) { if (path_is_valid(list_entry->local_core_path)) { core_installed = true; update_installed_handle->installed_index = update_installed_handle->list_index; update_installed_handle->status = UPDATE_INSTALLED_CORES_UPDATE_CORE; } } /* Update progress display */ task_free_title(task); if (core_installed) { char task_title[PATH_MAX_LENGTH]; strlcpy( task_title, msg_hash_to_str(MSG_CHECKING_CORE), sizeof(task_title)); strlcat(task_title, list_entry->display_name, sizeof(task_title)); task_set_title(task, strdup(task_title)); } else task_set_title(task, strdup(msg_hash_to_str(MSG_SCANNING_CORES))); task_set_progress(task, (update_installed_handle->list_index * 100) / update_installed_handle->list_size); /* Increment list index */ update_installed_handle->list_index++; } break; case UPDATE_INSTALLED_CORES_UPDATE_CORE: { const core_updater_list_entry_t *list_entry = NULL; uint32_t local_crc = 0; /* Get list entry * > In the event of an error, just return * to UPDATE_INSTALLED_CORES_ITERATE state */ if (!core_updater_list_get_index( update_installed_handle->core_list, update_installed_handle->installed_index, &list_entry)) { update_installed_handle->status = UPDATE_INSTALLED_CORES_ITERATE; break; } /* Check whether core is locked * > Have to set validate_path to 'false' here, * since this does not run on the main thread * > Validation is not required anyway, since core * updater list provides 'sane' core paths */ if (core_info_get_core_lock(list_entry->local_core_path, false)) { RARCH_LOG("[core updater] Skipping locked core: %s\n", list_entry->display_name); /* Core update is disabled * > Just increment 'locked cores' counter and * return to UPDATE_INSTALLED_CORES_ITERATE state */ update_installed_handle->num_locked++; update_installed_handle->status = UPDATE_INSTALLED_CORES_ITERATE; break; } /* Get CRC of existing core */ { const char *local_core_path = list_entry->local_core_path; if ( !string_is_empty(local_core_path) && path_is_valid (local_core_path) ) local_crc = task_core_updater_get_core_crc( local_core_path); } /* Check whether existing core and remote core * have the same CRC * > If CRC matches, then core is already the most * recent version - just return to * UPDATE_INSTALLED_CORES_ITERATE state */ if ((local_crc != 0) && (local_crc == list_entry->crc)) { update_installed_handle->status = UPDATE_INSTALLED_CORES_ITERATE; break; } /* Existing core is not the most recent version * > Request download */ update_installed_handle->download_task = (retro_task_t*) task_push_core_updater_download( update_installed_handle->core_list, list_entry->remote_filename, local_crc, true, update_installed_handle->auto_backup, update_installed_handle->auto_backup_history_size, update_installed_handle->path_dir_libretro, update_installed_handle->path_dir_core_assets); /* Again, if an error occurred, just return to * UPDATE_INSTALLED_CORES_ITERATE state */ if (!update_installed_handle->download_task) update_installed_handle->status = UPDATE_INSTALLED_CORES_ITERATE; else { char task_title[PATH_MAX_LENGTH]; /* Update task title */ task_free_title(task); strlcpy( task_title, msg_hash_to_str(MSG_UPDATING_CORE), sizeof(task_title)); strlcat(task_title, list_entry->display_name, sizeof(task_title)); task_set_title(task, strdup(task_title)); /* Increment 'updated cores' counter */ update_installed_handle->num_updated++; /* Wait for download to complete */ update_installed_handle->status = UPDATE_INSTALLED_CORES_WAIT_DOWNLOAD; } } break; case UPDATE_INSTALLED_CORES_WAIT_DOWNLOAD: { bool download_complete = false; /* > If task is running, check 'is finished' * status * > If task is NULL, then it is finished * by definition */ if (update_installed_handle->download_task) download_complete = task_get_finished(update_installed_handle->download_task); else download_complete = true; /* If download is complete, return to * UPDATE_INSTALLED_CORES_ITERATE state */ if (download_complete) { update_installed_handle->download_task = NULL; update_installed_handle->status = UPDATE_INSTALLED_CORES_ITERATE; } } break; case UPDATE_INSTALLED_CORES_END: { /* Set final task title */ task_free_title(task); /* > Check whether core list was fetched * successfully */ if (update_installed_handle->list_size > 0) { char task_title[PATH_MAX_LENGTH]; size_t _len = strlcpy(task_title, msg_hash_to_str(MSG_ALL_CORES_UPDATED), sizeof(task_title)); /* > Generate final status message based on number * of cores that were updated/locked */ if (update_installed_handle->num_updated > 0) { if (update_installed_handle->num_locked > 0) snprintf( task_title + _len, sizeof(task_title) - _len, " [%s%u, %s%u]", msg_hash_to_str(MSG_NUM_CORES_UPDATED), update_installed_handle->num_updated, msg_hash_to_str(MSG_NUM_CORES_LOCKED), update_installed_handle->num_locked); else snprintf( task_title + _len, sizeof(task_title) - _len, " [%s%u]", msg_hash_to_str(MSG_NUM_CORES_UPDATED), update_installed_handle->num_updated); } else if (update_installed_handle->num_locked > 0) snprintf( task_title + _len, sizeof(task_title) - _len, " [%s%u]", msg_hash_to_str(MSG_NUM_CORES_LOCKED), update_installed_handle->num_locked); task_set_title(task, strdup(task_title)); } else task_set_title(task, strdup(msg_hash_to_str(MSG_CORE_LIST_FAILED))); } /* fall-through */ default: task_set_progress(task, 100); goto task_finished; } return; task_finished: if (task) task_set_finished(task, true); if (update_installed_handle) free_update_installed_cores_handle(update_installed_handle); } static void task_update_single_core_handler(retro_task_t *task) { update_single_core_handle_t *handle = (update_single_core_handle_t*)task->state; switch (handle->status) { case UPDATE_SINGLE_CORE_BEGIN: { if (task_push_get_core_updater_list(handle->core_list, true, false)) handle->status = UPDATE_SINGLE_CORE_WAIT_LIST; else handle->status = UPDATE_SINGLE_CORE_END; } break; case UPDATE_SINGLE_CORE_WAIT_LIST: { task_finder_data_t find_data; find_data.func = task_core_updater_get_list_finder; find_data.userdata = handle->core_list; if (!task_queue_find(&find_data)) handle->status = UPDATE_SINGLE_CORE_UPDATE_CORE; } break; case UPDATE_SINGLE_CORE_UPDATE_CORE: { uint32_t crc = 0; const core_updater_list_entry_t *entry = NULL; if (!core_updater_list_get_core(handle->core_list, handle->path_core, &entry)) { handle->status = UPDATE_SINGLE_CORE_END; break; } if (core_info_get_core_lock(entry->local_core_path, false)) { handle->status = UPDATE_SINGLE_CORE_END; break; } { const char *local_core_path = entry->local_core_path; if ( !string_is_empty(local_core_path) && path_is_valid (local_core_path) ) crc = task_core_updater_get_core_crc(local_core_path); } if (!crc || crc == entry->crc) { handle->status = UPDATE_SINGLE_CORE_END; break; } if (task_push_core_updater_download(handle->core_list, entry->remote_filename, crc, true, handle->auto_backup, handle->auto_backup_history_size, handle->path_dir_libretro, handle->path_dir_core_assets)) handle->status = UPDATE_SINGLE_CORE_WAIT_DOWNLOAD; else handle->status = UPDATE_SINGLE_CORE_END; } break; case UPDATE_SINGLE_CORE_WAIT_DOWNLOAD: { task_finder_data_t find_data; const core_updater_list_entry_t *entry = NULL; if (!core_updater_list_get_core(handle->core_list, handle->path_core, &entry)) { handle->status = UPDATE_SINGLE_CORE_END; break; } find_data.func = task_core_updater_download_finder; find_data.userdata = entry->remote_filename; if (!task_queue_find(&find_data)) handle->status = UPDATE_SINGLE_CORE_END; } break; case UPDATE_SINGLE_CORE_END: default: task_set_progress(task, 100); task_set_finished(task, true); break; } } static void task_update_single_core_cleanup(retro_task_t *task) { update_single_core_handle_t *handle = (update_single_core_handle_t*)task->state; core_updater_list_free(handle->core_list); free(handle); } static bool task_update_installed_cores_finder(retro_task_t *task, void *user_data) { if (task) if ( task->handler == task_update_installed_cores_handler || task->handler == task_update_single_core_handler) return true; return false; } 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) { task_finder_data_t find_data; retro_task_t *task = NULL; update_installed_cores_handle_t *update_installed_handle = (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)) goto error; /* Configure handle */ update_installed_handle->auto_backup = auto_backup; update_installed_handle->auto_backup_history_size = auto_backup_history_size; update_installed_handle->path_dir_libretro = strdup(path_dir_libretro); update_installed_handle->path_dir_core_assets = string_is_empty(path_dir_core_assets) ? NULL : strdup(path_dir_core_assets); update_installed_handle->core_list = core_updater_list_init(); update_installed_handle->list_task = NULL; update_installed_handle->download_task = NULL; update_installed_handle->list_size = 0; update_installed_handle->list_index = 0; update_installed_handle->installed_index = 0; update_installed_handle->num_updated = 0; update_installed_handle->num_locked = 0; update_installed_handle->status = UPDATE_INSTALLED_CORES_BEGIN; if (!update_installed_handle->core_list) goto error; /* Only one instance of this task may run at a time */ find_data.func = task_update_installed_cores_finder; find_data.userdata = NULL; if (task_queue_find(&find_data)) goto error; /* Create task */ if (!(task = task_init())) goto error; /* Configure task */ task->handler = task_update_installed_cores_handler; task->state = update_installed_handle; task->title = strdup(msg_hash_to_str(MSG_FETCHING_CORE_LIST)); task->alternative_look = true; task->progress = 0; /* Push task */ task_queue_push(task); return; error: /* Clean up task */ if (task) { free(task); task = NULL; } /* Clean up handle */ if (update_installed_handle) free_update_installed_cores_handle(update_installed_handle); } bool task_push_update_single_core( const char *path_core, bool auto_backup, size_t auto_backup_history_size, const char *path_dir_libretro, const char *path_dir_core_assets) { task_finder_data_t find_data; core_updater_list_t *core_list; update_single_core_handle_t *handle; retro_task_t *task; if (string_is_empty(path_core) || string_is_empty(path_dir_libretro)) return false; #ifdef ANDROID /* Regular core updater is disabled in Play Store builds. */ if (play_feature_delivery_enabled()) return false; #endif /* Only one instance of this task may run at a time. */ find_data.func = task_update_installed_cores_finder; find_data.userdata = NULL; if (task_queue_find(&find_data)) return false; core_list = core_updater_list_init(); handle = (update_single_core_handle_t*)malloc(sizeof(*handle)); task = task_init(); if (!core_list || !handle || !task) { core_updater_list_free(core_list); free(handle); free(task); return false; } /* Configure handle */ handle->status = UPDATE_SINGLE_CORE_BEGIN; handle->core_list = core_list; handle->auto_backup = auto_backup; handle->auto_backup_history_size = auto_backup_history_size; strlcpy(handle->path_core, path_core, sizeof(handle->path_core)); strlcpy(handle->path_dir_libretro, path_dir_libretro, sizeof(handle->path_dir_libretro)); if (!string_is_empty(path_dir_core_assets)) strlcpy(handle->path_dir_core_assets, path_dir_core_assets, sizeof(handle->path_dir_core_assets)); else handle->path_dir_core_assets[0] = '\0'; /* Configure task */ task->handler = task_update_single_core_handler; task->cleanup = task_update_single_core_cleanup; task->state = handle; /* Push task */ task_queue_push(task); return true; } #if defined(ANDROID) /**************************************/ /* Play feature delivery core install */ /**************************************/ static void free_play_feature_delivery_install_handle( play_feature_delivery_install_handle_t *pfd_install_handle) { 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->backup_core_path) free(pfd_install_handle->backup_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; if (!(pfd_install_handle = (play_feature_delivery_install_handle_t*)task->state)) 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 remove it before attempting * play feature delivery transaction */ if (path_is_valid(pfd_install_handle->local_core_path)) { char backup_core_path[PATH_MAX_LENGTH]; bool backup_successful = false; /* Have to create a backup, in case install * process fails * > Note: since only one install task can * run at a time, a UID is not required */ /* Generate backup file name */ strlcpy(backup_core_path, pfd_install_handle->local_core_path, sizeof(backup_core_path)); strlcat(backup_core_path, FILE_PATH_BACKUP_EXTENSION, sizeof(backup_core_path)); if (!string_is_empty(backup_core_path)) { int ret; /* If an old backup file exists (i.e. leftovers * from a mid-task crash/user exit), delete it */ if (path_is_valid(backup_core_path)) filestream_delete(backup_core_path); /* Attempt to rename core file */ ret = filestream_rename( pfd_install_handle->local_core_path, backup_core_path); if (!ret) { /* Success - cache backup file name */ pfd_install_handle->backup_core_path = strdup(backup_core_path); backup_successful = true; } } /* If backup failed, all we can do is delete * the existing core file... */ if (!backup_successful && 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: { enum play_feature_delivery_install_status install_status; unsigned install_progress; char task_title[PATH_MAX_LENGTH]; /* Get current install status */ bool 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]; /* 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)); /* Check whether a core backup file was created */ if (!string_is_empty(pfd_install_handle->backup_core_path) && path_is_valid(pfd_install_handle->backup_core_path)) { /* If install was successful, delete backup */ if (pfd_install_handle->success) filestream_delete(pfd_install_handle->backup_core_path); else { /* Otherwise, attempt to restore backup */ int ret = filestream_rename( pfd_install_handle->backup_core_path, pfd_install_handle->local_core_path); /* If restore failed, all we can do is attempt * to delete the backup... */ if (ret && path_is_valid(pfd_install_handle->backup_core_path)) filestream_delete(pfd_install_handle->backup_core_path); } } /* If task is muted and install failed, set * error string (allows status to be checked * externally) */ if (!pfd_install_handle->success && task_get_mute(task)) task_set_error(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); if (pfd_install_handle) 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) { return (task && task->handler == task_play_feature_delivery_core_install_handler); } void *task_push_play_feature_delivery_core_install( core_updater_list_t* core_list, const char *filename, bool mute) { 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)); /* 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->backup_core_path = NULL; 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 */ if (!(task = task_init())) 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 = mute; 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 (retroarch_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 task; error: /* Clean up task */ if (task) { free(task); task = NULL; } /* Clean up handle */ if (pfd_install_handle) free_play_feature_delivery_install_handle(pfd_install_handle); return NULL; } /************************************************/ /* Play feature delivery switch installed cores */ /************************************************/ static void free_play_feature_delivery_switch_cores_handle( play_feature_delivery_switch_cores_handle_t *pfd_switch_cores_handle) { if (pfd_switch_cores_handle->path_dir_libretro) free(pfd_switch_cores_handle->path_dir_libretro); if (pfd_switch_cores_handle->path_libretro_info) free(pfd_switch_cores_handle->path_libretro_info); if (pfd_switch_cores_handle->error_msg) free(pfd_switch_cores_handle->error_msg); core_updater_list_free(pfd_switch_cores_handle->core_list); free(pfd_switch_cores_handle); pfd_switch_cores_handle = NULL; } static void task_play_feature_delivery_switch_cores_handler( retro_task_t *task) { play_feature_delivery_switch_cores_handle_t *pfd_switch_cores_handle = NULL; if (!task) goto task_finished; if (!(pfd_switch_cores_handle = (play_feature_delivery_switch_cores_handle_t*)task->state)) goto task_finished; if (task_get_cancelled(task)) goto task_finished; switch (pfd_switch_cores_handle->status) { case PLAY_FEATURE_DELIVERY_SWITCH_CORES_BEGIN: { /* Query available cores * Note: It should never be possible for this * function (or the subsequent parsing of its * output) to fail. We handle error conditions * regardless, but there is no need to perform * detailed checking - just report any problems * to the user as a generic 'failed to retrieve * core list' error */ struct string_list *available_cores = play_feature_delivery_available_cores(); bool success = false; if (!available_cores) { pfd_switch_cores_handle->status = PLAY_FEATURE_DELIVERY_SWITCH_CORES_END; break; } /* Populate core updater list */ success = core_updater_list_parse_pfd_data( pfd_switch_cores_handle->core_list, pfd_switch_cores_handle->path_dir_libretro, pfd_switch_cores_handle->path_libretro_info, available_cores); string_list_free(available_cores); /* Cache list size */ if (success) pfd_switch_cores_handle->list_size = core_updater_list_size(pfd_switch_cores_handle->core_list); if (pfd_switch_cores_handle->list_size < 1) pfd_switch_cores_handle->status = PLAY_FEATURE_DELIVERY_SWITCH_CORES_END; else pfd_switch_cores_handle->status = PLAY_FEATURE_DELIVERY_SWITCH_CORES_ITERATE; } break; case PLAY_FEATURE_DELIVERY_SWITCH_CORES_ITERATE: { const core_updater_list_entry_t *list_entry = NULL; bool core_installed = false; /* Check whether we have reached the end * of the list */ if (pfd_switch_cores_handle->list_index >= pfd_switch_cores_handle->list_size) { pfd_switch_cores_handle->status = PLAY_FEATURE_DELIVERY_SWITCH_CORES_END; break; } /* Check whether current core is installed */ if (core_updater_list_get_index( pfd_switch_cores_handle->core_list, pfd_switch_cores_handle->list_index, &list_entry) && path_is_valid(list_entry->local_core_path)) { core_installed = true; pfd_switch_cores_handle->installed_index = pfd_switch_cores_handle->list_index; pfd_switch_cores_handle->status = PLAY_FEATURE_DELIVERY_SWITCH_CORES_INSTALL_CORE; } /* Update progress display */ task_free_title(task); if (core_installed) { char task_title[PATH_MAX_LENGTH]; strlcpy(task_title, msg_hash_to_str(MSG_CHECKING_CORE), sizeof(task_title)); strlcat(task_title, list_entry->display_name, sizeof(task_title)); task_set_title(task, strdup(task_title)); } else task_set_title(task, strdup(msg_hash_to_str(MSG_SCANNING_CORES))); task_set_progress(task, (pfd_switch_cores_handle->list_index * 100) / pfd_switch_cores_handle->list_size); /* Increment list index */ pfd_switch_cores_handle->list_index++; } break; case PLAY_FEATURE_DELIVERY_SWITCH_CORES_INSTALL_CORE: { const core_updater_list_entry_t *list_entry = NULL; /* Get list entry * > In the event of an error, just return * to PLAY_FEATURE_DELIVERY_SWITCH_CORES_ITERATE * state */ if (!core_updater_list_get_index( pfd_switch_cores_handle->core_list, pfd_switch_cores_handle->installed_index, &list_entry)) { pfd_switch_cores_handle->status = PLAY_FEATURE_DELIVERY_SWITCH_CORES_ITERATE; break; } /* Check whether core is already installed via * play feature delivery */ if (play_feature_delivery_core_installed( list_entry->remote_filename)) { pfd_switch_cores_handle->status = PLAY_FEATURE_DELIVERY_SWITCH_CORES_ITERATE; break; } /* Existing core is not installed via * play feature delivery * > Request installation/replacement */ pfd_switch_cores_handle->install_task = (retro_task_t*) task_push_play_feature_delivery_core_install( pfd_switch_cores_handle->core_list, list_entry->remote_filename, true); /* Again, if an error occurred, just return to * PLAY_FEATURE_DELIVERY_SWITCH_CORES_ITERATE * state */ if (!pfd_switch_cores_handle->install_task) pfd_switch_cores_handle->status = PLAY_FEATURE_DELIVERY_SWITCH_CORES_ITERATE; else { char task_title[PATH_MAX_LENGTH]; /* Update task title */ task_free_title(task); strlcpy(task_title, msg_hash_to_str(MSG_UPDATING_CORE), sizeof(task_title)); strlcat(task_title, list_entry->display_name, sizeof(task_title)); task_set_title(task, strdup(task_title)); /* Wait for installation to complete */ pfd_switch_cores_handle->status = PLAY_FEATURE_DELIVERY_SWITCH_CORES_WAIT_INSTALL; } } break; case PLAY_FEATURE_DELIVERY_SWITCH_CORES_WAIT_INSTALL: { bool install_complete = false; const char* error_msg = NULL; /* > If task is running, check 'is finished' status * > If task is NULL, then it is finished by * definition */ if (pfd_switch_cores_handle->install_task) { error_msg = task_get_error( pfd_switch_cores_handle->install_task); install_complete = task_get_finished( pfd_switch_cores_handle->install_task); } else install_complete = true; /* Check for installation errors * > These should be considered 'serious', and * will trigger the task to end early */ if (!string_is_empty(error_msg)) { pfd_switch_cores_handle->error_msg = strdup(error_msg); pfd_switch_cores_handle->install_task = NULL; pfd_switch_cores_handle->status = PLAY_FEATURE_DELIVERY_SWITCH_CORES_END; break; } /* If installation is complete, return to * PLAY_FEATURE_DELIVERY_SWITCH_CORES_ITERATE * state */ if (install_complete) { pfd_switch_cores_handle->install_task = NULL; pfd_switch_cores_handle->status = PLAY_FEATURE_DELIVERY_SWITCH_CORES_ITERATE; } } break; case PLAY_FEATURE_DELIVERY_SWITCH_CORES_END: { const char *task_title = msg_hash_to_str(MSG_CORE_LIST_FAILED); /* Set final task title */ task_free_title(task); /* > Check whether core list was generated * successfully */ if (pfd_switch_cores_handle->list_size > 0) { /* Check whether any installation errors occurred */ if (!string_is_empty(pfd_switch_cores_handle->error_msg)) task_title = pfd_switch_cores_handle->error_msg; else task_title = msg_hash_to_str(MSG_ALL_CORES_SWITCHED_PFD); } 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); if (pfd_switch_cores_handle) free_play_feature_delivery_switch_cores_handle(pfd_switch_cores_handle); } static bool task_play_feature_delivery_switch_cores_finder( retro_task_t *task, void *user_data) { return (task && task->handler == task_play_feature_delivery_switch_cores_handler); } void task_push_play_feature_delivery_switch_installed_cores( const char *path_dir_libretro, const char *path_libretro_info) { task_finder_data_t find_data; retro_task_t *task = NULL; play_feature_delivery_switch_cores_handle_t *pfd_switch_cores_handle = (play_feature_delivery_switch_cores_handle_t*) calloc(1, sizeof(play_feature_delivery_switch_cores_handle_t)); /* Sanity check */ if ( string_is_empty(path_dir_libretro) || string_is_empty(path_libretro_info) || !pfd_switch_cores_handle || !play_feature_delivery_enabled()) goto error; /* Only one instance of this task my run at a time */ find_data.func = task_play_feature_delivery_switch_cores_finder; find_data.userdata = NULL; if (task_queue_find(&find_data)) goto error; /* Configure handle */ pfd_switch_cores_handle->path_dir_libretro = strdup(path_dir_libretro); pfd_switch_cores_handle->path_libretro_info = strdup(path_libretro_info); pfd_switch_cores_handle->error_msg = NULL; pfd_switch_cores_handle->core_list = core_updater_list_init(); pfd_switch_cores_handle->install_task = NULL; pfd_switch_cores_handle->list_size = 0; pfd_switch_cores_handle->list_index = 0; pfd_switch_cores_handle->installed_index = 0; pfd_switch_cores_handle->status = PLAY_FEATURE_DELIVERY_SWITCH_CORES_BEGIN; if (!pfd_switch_cores_handle->core_list) goto error; /* Create task */ if (!(task = task_init())) goto error; /* Configure task */ task->handler = task_play_feature_delivery_switch_cores_handler; task->state = pfd_switch_cores_handle; task->title = strdup(msg_hash_to_str(MSG_SCANNING_CORES)); task->alternative_look = true; task->progress = 0; /* Push task */ task_queue_push(task); return; error: /* Clean up task */ if (task) { free(task); task = NULL; } /* Clean up handle */ if (pfd_switch_cores_handle) free_play_feature_delivery_switch_cores_handle(pfd_switch_cores_handle); } #endif