diff --git a/Makefile.common b/Makefile.common
index c1bd19d9b9..8fcbc288c9 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -186,6 +186,7 @@ OBJ += frontend/frontend_driver.o \
tasks/task_image.o \
tasks/task_playlist_manager.o \
tasks/task_manual_content_scan.o \
+ tasks/task_core_backup.o \
$(LIBRETRO_COMM_DIR)/encodings/encoding_utf.o \
$(LIBRETRO_COMM_DIR)/encodings/encoding_crc32.o \
$(LIBRETRO_COMM_DIR)/encodings/encoding_base64.o \
@@ -251,6 +252,7 @@ OBJ += \
$(LIBRETRO_COMM_DIR)/compat/compat_posix_string.o \
managers/cheat_manager.o \
core_info.o \
+ core_backup.o \
$(LIBRETRO_COMM_DIR)/file/config_file.o \
$(LIBRETRO_COMM_DIR)/file/config_file_userdata.o \
runtime_file.o \
@@ -272,6 +274,7 @@ OBJ += \
$(LIBRETRO_COMM_DIR)/features/features_cpu.o \
verbosity.o \
$(LIBRETRO_COMM_DIR)/playlists/label_sanitization.o \
+ $(LIBRETRO_COMM_DIR)/time/rtime.o \
manual_content_scan.o \
disk_control_interface.o
diff --git a/core_backup.c b/core_backup.c
new file mode 100644
index 0000000000..61f5dd28a7
--- /dev/null
+++ b/core_backup.c
@@ -0,0 +1,740 @@
+/* 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "frontend/frontend_driver.h"
+#include "file_path_special.h"
+#include "core_info.h"
+#include "verbosity.h"
+
+#include "core_backup.h"
+
+/* Holds all entries in a core backup list */
+struct core_backup_list
+{
+ size_t size;
+ size_t capacity;
+ core_backup_list_entry_t *entries;
+};
+
+/*********************/
+/* Utility Functions */
+/*********************/
+
+/* Generates backup directory path for specified core.
+ * Returns false if 'core_path' and/or 'dir_core_assets'
+ * are invalid, or a filesystem error occurs */
+static bool core_backup_get_backup_dir(
+ const char *dir_libretro, const char *dir_core_assets,
+ const char *core_filename,
+ char *backup_dir, size_t len)
+{
+ char *last_underscore = NULL;
+ char core_file_id[PATH_MAX_LENGTH];
+ char tmp[PATH_MAX_LENGTH];
+
+ core_file_id[0] = '\0';
+ tmp[0] = '\0';
+
+ /* Extract core file 'ID' (name without extension + suffix)
+ * from core path */
+ if (string_is_empty(dir_libretro) ||
+ string_is_empty(core_filename) ||
+ (len < 1))
+ return false;
+
+ strlcpy(core_file_id, core_filename, sizeof(core_file_id));
+
+ /* > Remove file extension */
+ path_remove_extension(core_file_id);
+
+ if (string_is_empty(core_file_id))
+ return false;
+
+ /* > Remove platform-specific file name suffix,
+ * if required */
+ last_underscore = strrchr(core_file_id, '_');
+
+ if (!string_is_empty(last_underscore))
+ if (!string_is_equal(last_underscore, "_libretro"))
+ *last_underscore = '\0';
+
+ if (string_is_empty(core_file_id))
+ return false;
+
+ /* Get core backup directory
+ * > If no assets directory is defined, use
+ * core directory as a base */
+ fill_pathname_join(tmp, string_is_empty(dir_core_assets) ?
+ dir_libretro : dir_core_assets,
+ "core_backups", sizeof(tmp));
+
+ fill_pathname_join(backup_dir, tmp,
+ core_file_id, len);
+
+ if (string_is_empty(backup_dir))
+ return false;
+
+ /* > Create directory, if required */
+ if (!path_is_directory(backup_dir))
+ {
+ if (!path_mkdir(backup_dir))
+ {
+ RARCH_ERR("[core backup] Failed to create backup directory: %s.\n", backup_dir);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/* Generates a timestamped core backup file path from
+ * the specified core path. Returns true if successful */
+bool core_backup_get_backup_path(
+ const char *core_path, uint32_t crc, enum core_backup_mode backup_mode,
+ const char *dir_core_assets, char *backup_path, size_t len)
+{
+ int n;
+ time_t current_time;
+ struct tm time_info;
+ const char *core_filename = NULL;
+ char core_dir[PATH_MAX_LENGTH];
+ char backup_dir[PATH_MAX_LENGTH];
+ char backup_filename[PATH_MAX_LENGTH];
+
+ core_dir[0] = '\0';
+ backup_dir[0] = '\0';
+ backup_filename[0] = '\0';
+
+ /* Get core filename and parent directory */
+ if (string_is_empty(core_path))
+ return false;
+
+ core_filename = path_basename(core_path);
+
+ if (string_is_empty(core_filename))
+ return false;
+
+ fill_pathname_parent_dir(core_dir, core_path, sizeof(core_dir));
+
+ if (string_is_empty(core_dir))
+ return false;
+
+ /* Get backup directory */
+ if (!core_backup_get_backup_dir(core_dir, dir_core_assets, core_filename,
+ backup_dir, sizeof(backup_dir)))
+ return false;
+
+ /* Get current time */
+ time(¤t_time);
+ rtime_localtime(¤t_time, &time_info);
+
+ /* Generate backup filename */
+ n = snprintf(backup_filename, sizeof(backup_filename),
+ "%s.%04u%02u%02uT%02u%02u%02u.%08x.%u%s",
+ core_filename,
+ (unsigned)time_info.tm_year + 1900,
+ (unsigned)time_info.tm_mon + 1,
+ (unsigned)time_info.tm_mday,
+ (unsigned)time_info.tm_hour,
+ (unsigned)time_info.tm_min,
+ (unsigned)time_info.tm_sec,
+ crc,
+ (unsigned)backup_mode,
+ file_path_str(FILE_PATH_CORE_BACKUP_EXTENSION));
+
+ if ((n < 0) || (n >= 128))
+ n = 0; /* Silence GCC warnings... */
+
+ /* Build final path */
+ fill_pathname_join(backup_path, backup_dir,
+ backup_filename, len);
+
+ return true;
+}
+
+/* Returns detected type of specified core backup file */
+enum core_backup_type core_backup_get_backup_type(const char *backup_path)
+{
+ const char *backup_ext = NULL;
+ struct string_list *metadata_list = NULL;
+ char core_ext[255];
+
+ core_ext[0] = '\0';
+
+ if (string_is_empty(backup_path) || !path_is_valid(backup_path))
+ goto error;
+
+ /* Get backup file extension */
+ backup_ext = path_get_extension(backup_path);
+
+ if (string_is_empty(backup_ext))
+ goto error;
+
+ /* Get platform-specific dynamic library extension */
+ if (!frontend_driver_get_core_extension(core_ext, sizeof(core_ext)))
+ goto error;
+
+ /* Check if this is an archived backup */
+ if (string_is_equal_noncase(backup_ext,
+ file_path_str(FILE_PATH_CORE_BACKUP_EXTENSION_NO_DOT)))
+ {
+ const char *backup_filename = NULL;
+ const char *src_ext = NULL;
+
+ /* Split the backup filename into its various
+ * metadata components */
+ backup_filename = path_basename(backup_path);
+
+ if (string_is_empty(backup_filename))
+ goto error;
+
+ metadata_list = string_split(backup_filename, ".");
+
+ if (!metadata_list || (metadata_list->size != 6))
+ goto error;
+
+ /* Get extension of source core file */
+ src_ext = metadata_list->elems[1].data;
+
+ if (string_is_empty(src_ext))
+ goto error;
+
+ /* Check whether extension is valid */
+ if (!string_is_equal_noncase(src_ext, core_ext))
+ goto error;
+
+ string_list_free(metadata_list);
+ metadata_list = NULL;
+
+ return CORE_BACKUP_TYPE_ARCHIVE;
+ }
+
+ /* Check if this is a plain dynamic library file */
+ if (string_is_equal_noncase(backup_ext, core_ext))
+ return CORE_BACKUP_TYPE_LIB;
+
+error:
+ if (metadata_list)
+ {
+ string_list_free(metadata_list);
+ metadata_list = NULL;
+ }
+
+ return CORE_BACKUP_TYPE_INVALID;
+}
+
+/* Fetches crc value of specified core backup file.
+ * Returns true if successful */
+bool core_backup_get_backup_crc(char *backup_path, uint32_t *crc)
+{
+ struct string_list *metadata_list = NULL;
+ enum core_backup_type backup_type;
+
+ if (string_is_empty(backup_path) || !crc)
+ goto error;
+
+ /* Get backup type */
+ backup_type = core_backup_get_backup_type(backup_path);
+
+ switch (backup_type)
+ {
+ case CORE_BACKUP_TYPE_ARCHIVE:
+ {
+ const char *backup_filename = NULL;
+ const char *crc_str = NULL;
+
+ /* Split the backup filename into its various
+ * metadata components */
+ backup_filename = path_basename(backup_path);
+
+ if (string_is_empty(backup_filename))
+ goto error;
+
+ metadata_list = string_split(backup_filename, ".");
+
+ if (!metadata_list || (metadata_list->size != 6))
+ goto error;
+
+ /* Get crc string */
+ crc_str = metadata_list->elems[3].data;
+
+ if (string_is_empty(crc_str))
+ goto error;
+
+ /* Convert to an integer */
+ *crc = (uint32_t)string_hex_to_unsigned(crc_str);
+
+ if (*crc == 0)
+ goto error;
+
+ string_list_free(metadata_list);
+ metadata_list = NULL;
+
+ return true;
+ }
+ break;
+ case CORE_BACKUP_TYPE_LIB:
+ {
+ intfstream_t *backup_file = NULL;
+
+ /* This is a plain dynamic library file,
+ * have to read file data to determine crc */
+
+ /* Open backup file */
+ backup_file = intfstream_open_file(
+ backup_path, RETRO_VFS_FILE_ACCESS_READ,
+ RETRO_VFS_FILE_ACCESS_HINT_NONE);
+
+ if (backup_file)
+ {
+ bool success;
+
+ /* Get crc value */
+ success = intfstream_get_crc(backup_file, crc);
+
+ /* Close backup file */
+ intfstream_close(backup_file);
+ free(backup_file);
+ backup_file = NULL;
+
+ return success;
+ }
+ }
+ break;
+ default:
+ /* Backup is invalid */
+ break;
+ }
+
+error:
+ if (metadata_list)
+ {
+ string_list_free(metadata_list);
+ metadata_list = NULL;
+ }
+
+ return false;
+}
+
+/* Fetches core path associated with specified core
+ * backup file. Returns detected type of backup
+ * file - CORE_BACKUP_TYPE_INVALID indicates that
+ * backup file cannot be restored/installed, or
+ * arguments are otherwise invalid */
+enum core_backup_type core_backup_get_core_path(
+ const char *backup_path, const char *dir_libretro,
+ char *core_path, size_t len)
+{
+ const char *backup_filename = NULL;
+ char *core_filename = NULL;
+ enum core_backup_type backup_type = CORE_BACKUP_TYPE_INVALID;
+
+ if (string_is_empty(backup_path) || string_is_empty(dir_libretro))
+ return backup_type;
+
+ backup_filename = path_basename(backup_path);
+
+ if (string_is_empty(backup_filename))
+ return backup_type;
+
+ /* Check backup type */
+ switch (core_backup_get_backup_type(backup_path))
+ {
+ case CORE_BACKUP_TYPE_ARCHIVE:
+ {
+ char *period = NULL;
+
+ /* This is an archived backup with timestamp/crc
+ * metadata in the filename */
+ core_filename = strdup(backup_filename);
+
+ /* Find the location of the second period */
+ period = strchr(core_filename, '.');
+ if (!period || (*(++period) == '\0'))
+ break;
+
+ period = strchr(period, '.');
+ if (!period)
+ break;
+
+ /* Trim everything after (and including) the
+ * second period */
+ *period = '\0';
+
+ if (string_is_empty(core_filename))
+ break;
+
+ /* All good - build core path */
+ fill_pathname_join(core_path, dir_libretro,
+ core_filename, len);
+
+ backup_type = CORE_BACKUP_TYPE_ARCHIVE;
+ }
+ break;
+ case CORE_BACKUP_TYPE_LIB:
+ /* This is a plain dynamic library file */
+ fill_pathname_join(core_path, dir_libretro,
+ backup_filename, len);
+ backup_type = CORE_BACKUP_TYPE_LIB;
+ break;
+ default:
+ /* Backup is invalid */
+ break;
+ }
+
+ if (core_filename)
+ {
+ free(core_filename);
+ core_filename = NULL;
+ }
+
+ return backup_type;
+}
+
+/*************************/
+/* Backup List Functions */
+/*************************/
+
+/**************************************/
+/* Initialisation / De-Initialisation */
+/**************************************/
+
+/* Parses backup file name and adds to backup list, if valid */
+static bool core_backup_add_entry(core_backup_list_t *backup_list,
+ const char *core_filename, const char *backup_path)
+{
+ char *backup_filename = NULL;
+ core_backup_list_entry_t *entry = NULL;
+ unsigned backup_mode = 0;
+
+ if (!backup_list ||
+ string_is_empty(core_filename) ||
+ string_is_empty(backup_path) ||
+ (backup_list->size >= backup_list->capacity))
+ goto error;
+
+ backup_filename = strdup(path_basename(backup_path));
+
+ if (string_is_empty(backup_filename))
+ goto error;
+
+ /* Ensure base backup filename matches core */
+ if (!string_starts_with(backup_filename, core_filename))
+ goto error;
+
+ /* Remove backup file extension */
+ path_remove_extension(backup_filename);
+
+ /* Parse backup filename metadata
+ * - ...
+ * - timestamp: YYYYMMDDTHHMMSS */
+ entry = &backup_list->entries[backup_list->size];
+
+ if (sscanf(backup_filename + strlen(core_filename),
+ ".%04u%02u%02uT%02u%02u%02u.%08x.%u",
+ &entry->date.year, &entry->date.month, &entry->date.day,
+ &entry->date.hour, &entry->date.minute, &entry->date.second,
+ &entry->crc, &backup_mode) != 8)
+ goto error;
+
+ entry->backup_mode = (enum core_backup_mode)backup_mode;
+
+ /* Cache backup path */
+ entry->backup_path = strdup(backup_path);
+ backup_list->size++;
+
+ free(backup_filename);
+
+ return true;
+
+error:
+ if (backup_filename)
+ free(backup_filename);
+
+ return false;
+}
+
+/* Creates a new core backup list containing entries
+ * for all existing backup files.
+ * Returns a handle to a new core_backup_list_t object
+ * on success, otherwise returns NULL. */
+core_backup_list_t *core_backup_list_init(
+ const char *core_path, const char *dir_core_assets)
+{
+ size_t i;
+ const char *core_filename = NULL;
+ struct string_list *dir_list = NULL;
+ core_backup_list_t *backup_list = NULL;
+ core_backup_list_entry_t *entries = NULL;
+ char core_dir[PATH_MAX_LENGTH];
+ char backup_dir[PATH_MAX_LENGTH];
+
+ core_dir[0] = '\0';
+ backup_dir[0] = '\0';
+
+ /* Get core filename and parent directory */
+ if (string_is_empty(core_path))
+ goto error;
+
+ core_filename = path_basename(core_path);
+
+ if (string_is_empty(core_filename))
+ goto error;
+
+ fill_pathname_parent_dir(core_dir, core_path, sizeof(core_dir));
+
+ if (string_is_empty(core_dir))
+ goto error;
+
+ /* Get backup directory */
+ if (!core_backup_get_backup_dir(core_dir, dir_core_assets, core_filename,
+ backup_dir, sizeof(backup_dir)))
+ goto error;
+
+ /* Get backup file list */
+ dir_list = dir_list_new(
+ backup_dir,
+ file_path_str(FILE_PATH_CORE_BACKUP_EXTENSION),
+ false, /* include_dirs */
+ false, /* include_hidden */
+ false, /* include_compressed */
+ false /* recursive */
+ );
+
+ /* Sanity check */
+ if (!dir_list)
+ goto error;
+
+ if (dir_list->size < 1)
+ goto error;
+
+ /* Ensure list is sorted in alphabetical order */
+ dir_list_sort(dir_list, true);
+
+ /* Create core backup list */
+ backup_list = (core_backup_list_t*)calloc(1, sizeof(*backup_list));
+
+ if (!backup_list)
+ goto error;
+
+ /* Create entries array
+ * (Note: Set this to the full size of the directory
+ * list - this may be larger than we need, but saves
+ * many inefficiencies later) */
+ entries = (core_backup_list_entry_t*)calloc(dir_list->size, sizeof(*entries));
+
+ if (!entries)
+ goto error;
+
+ backup_list->entries = entries;
+ backup_list->capacity = dir_list->size;
+ backup_list->size = 0;
+
+ /* Loop over backup files and parse file names */
+ for (i = 0; i < dir_list->size; i++)
+ {
+ const char *backup_path = dir_list->elems[i].data;
+ core_backup_add_entry(backup_list, core_filename, backup_path);
+ }
+
+ if (backup_list->size == 0)
+ goto error;
+
+ string_list_free(dir_list);
+
+ return backup_list;
+
+error:
+ if (dir_list)
+ string_list_free(dir_list);
+
+ if (backup_list)
+ core_backup_list_free(backup_list);
+
+ return NULL;
+}
+
+/* Frees specified core backup list */
+void core_backup_list_free(core_backup_list_t *backup_list)
+{
+ size_t i;
+
+ if (!backup_list)
+ return;
+
+ if (backup_list->entries)
+ {
+ for (i = 0; i < backup_list->size; i++)
+ {
+ core_backup_list_entry_t *entry = &backup_list->entries[i];
+
+ if (!entry)
+ continue;
+
+ if (entry->backup_path)
+ {
+ free(entry->backup_path);
+ entry->backup_path = NULL;
+ }
+ }
+
+ free(backup_list->entries);
+ backup_list->entries = NULL;
+ }
+
+ free(backup_list);
+}
+
+/***********/
+/* Getters */
+/***********/
+
+/* Returns number of entries in core backup list */
+size_t core_backup_list_size(core_backup_list_t *backup_list)
+{
+ if (!backup_list)
+ return 0;
+
+ return backup_list->size;
+}
+
+/* Fetches core backup list entry corresponding
+ * to the specified entry index.
+ * Returns false if index is invalid. */
+bool core_backup_list_get_index(
+ core_backup_list_t *backup_list,
+ size_t idx,
+ const core_backup_list_entry_t **entry)
+{
+ if (!backup_list || !backup_list->entries || !entry)
+ return false;
+
+ if (idx >= backup_list->size)
+ return false;
+
+ *entry = &backup_list->entries[idx];
+
+ if (*entry)
+ return true;
+
+ return false;
+}
+
+/* Fetches core backup list entry corresponding
+ * to the specified core crc checksum value.
+ * Note that 'manual' and 'auto' backups are
+ * considered independent - we only compare
+ * crc values for the specified backup_mode.
+ * Returns false if entry is not found. */
+bool core_backup_list_get_crc(
+ core_backup_list_t *backup_list,
+ uint32_t crc, enum core_backup_mode backup_mode,
+ const core_backup_list_entry_t **entry)
+{
+ size_t i;
+
+ if (!backup_list || !backup_list->entries || !entry)
+ return false;
+
+ for (i = 0; i < backup_list->size; i++)
+ {
+ core_backup_list_entry_t *current_entry = &backup_list->entries[i];
+
+ if (current_entry &&
+ (current_entry->crc == crc) &&
+ (current_entry->backup_mode == backup_mode))
+ {
+ *entry = current_entry;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* Fetches a string representation of a backup
+ * list entry timestamp.
+ * Returns false in the event of an error */
+bool core_backup_list_get_entry_timestamp_str(
+ const core_backup_list_entry_t *entry,
+ enum core_backup_date_separator_type date_separator,
+ char *timestamp, size_t len)
+{
+ int n;
+ const char *format_str = "";
+
+ if (!entry || (len < 20))
+ return false;
+
+ /* Get time format string */
+ switch (date_separator)
+ {
+ case CORE_BACKUP_DATE_SEPARATOR_SLASH:
+ format_str = "%04u/%02u/%02u %02u:%02u:%02u";
+ break;
+ case CORE_BACKUP_DATE_SEPARATOR_PERIOD:
+ format_str = "%04u.%02u.%02u %02u:%02u:%02u";
+ break;
+ default:
+ format_str = "%04u-%02u-%02u %02u:%02u:%02u";
+ break;
+ }
+
+ n = snprintf(timestamp, len,
+ format_str,
+ entry->date.year,
+ entry->date.month,
+ entry->date.day,
+ entry->date.hour,
+ entry->date.minute,
+ entry->date.second);
+
+ if ((n < 0) || (n >= 32))
+ n = 0; /* Silence GCC warnings... */
+
+ return true;
+}
+
+/* Fetches a string representation of a backup
+ * list entry crc value.
+ * Returns false in the event of an error */
+bool core_backup_list_get_entry_crc_str(
+ const core_backup_list_entry_t *entry,
+ char *crc, size_t len)
+{
+ int n;
+
+ if (!entry || (len < 9))
+ return false;
+
+ n = snprintf(crc, len, "%08x", entry->crc);
+
+ if ((n < 0) || (n >= 32))
+ n = 0; /* Silence GCC warnings... */
+
+ return true;
+}
diff --git a/core_backup.h b/core_backup.h
new file mode 100644
index 0000000000..d57ca89e32
--- /dev/null
+++ b/core_backup.h
@@ -0,0 +1,171 @@
+/* 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 __CORE_BACKUP_H
+#define __CORE_BACKUP_H
+
+#include
+#include
+
+#include
+
+RETRO_BEGIN_DECLS
+
+/* Defines the various types of supported core backup
+ * file. Allows us to handle manual core installs
+ * (via downloaded/compiled dynamic libraries dropped
+ * in the 'downloads' folder) using the same task
+ * interface as 'managed'/archived backups */
+enum core_backup_type
+{
+ CORE_BACKUP_TYPE_INVALID = 0,
+ CORE_BACKUP_TYPE_ARCHIVE,
+ CORE_BACKUP_TYPE_LIB
+};
+
+/* Used to distinguish manual and automatic
+ * core backups */
+enum core_backup_mode
+{
+ CORE_BACKUP_MODE_MANUAL = 0,
+ CORE_BACKUP_MODE_AUTO
+};
+
+/* Note: These must be kept synchronised with
+ * 'enum menu_timedate_date_separator_type' in
+ * 'menu_defines.h' */
+enum core_backup_date_separator_type
+{
+ CORE_BACKUP_DATE_SEPARATOR_HYPHEN = 0,
+ CORE_BACKUP_DATE_SEPARATOR_SLASH,
+ CORE_BACKUP_DATE_SEPARATOR_PERIOD,
+ CORE_BACKUP_DATE_SEPARATOR_LAST
+};
+
+/* Holds all timestamp info for a core backup file */
+typedef struct
+{
+ unsigned year;
+ unsigned month;
+ unsigned day;
+ unsigned hour;
+ unsigned minute;
+ unsigned second;
+} core_backup_list_date_t;
+
+/* Holds all info related to a core backup file */
+typedef struct
+{
+ char *backup_path;
+ core_backup_list_date_t date;
+ uint32_t crc;
+ enum core_backup_mode backup_mode;
+} core_backup_list_entry_t;
+
+/* Prevent direct access to core_backup_list_t
+ * members */
+typedef struct core_backup_list core_backup_list_t;
+
+/*********************/
+/* Utility Functions */
+/*********************/
+
+/* Generates a timestamped core backup file path from
+ * the specified core path. Returns true if successful */
+bool core_backup_get_backup_path(
+ const char *core_path, uint32_t crc, enum core_backup_mode backup_mode,
+ const char *dir_core_assets, char *backup_path, size_t len);
+
+/* Returns detected type of specified core backup file */
+enum core_backup_type core_backup_get_backup_type(const char *backup_path);
+
+/* Fetches crc value of specified core backup file.
+ * Returns true if successful */
+bool core_backup_get_backup_crc(char *backup_path, uint32_t *crc);
+
+/* Fetches core path associated with specified core
+ * backup file. Returns detected type of backup
+ * file - CORE_BACKUP_TYPE_INVALID indicates that
+ * backup file cannot be restored/installed, or
+ * arguments are otherwise invalid */
+enum core_backup_type core_backup_get_core_path(
+ const char *backup_path, const char *dir_libretro,
+ char *core_path, size_t len);
+
+/*************************/
+/* Backup List Functions */
+/*************************/
+
+/**************************************/
+/* Initialisation / De-Initialisation */
+/**************************************/
+
+/* Creates a new core backup list containing entries
+ * for all existing backup files.
+ * Returns a handle to a new core_backup_list_t object
+ * on success, otherwise returns NULL. */
+core_backup_list_t *core_backup_list_init(
+ const char *core_path, const char *dir_core_assets);
+
+/* Frees specified core backup list */
+void core_backup_list_free(core_backup_list_t *backup_list);
+
+/***********/
+/* Getters */
+/***********/
+
+/* Returns number of entries in core backup list */
+size_t core_backup_list_size(core_backup_list_t *backup_list);
+
+/* Fetches core backup list entry corresponding
+ * to the specified entry index.
+ * Returns false if index is invalid. */
+bool core_backup_list_get_index(
+ core_backup_list_t *backup_list,
+ size_t idx,
+ const core_backup_list_entry_t **entry);
+
+/* Fetches core backup list entry corresponding
+ * to the specified core crc checksum value.
+ * Note that 'manual' and 'auto' backups are
+ * considered independent - we only compare
+ * crc values for the specified backup_mode.
+ * Returns false if entry is not found. */
+bool core_backup_list_get_crc(
+ core_backup_list_t *backup_list,
+ uint32_t crc, enum core_backup_mode backup_mode,
+ const core_backup_list_entry_t **entry);
+
+/* Fetches a string representation of a backup
+ * list entry timestamp.
+ * Returns false in the event of an error */
+bool core_backup_list_get_entry_timestamp_str(
+ const core_backup_list_entry_t *entry,
+ enum core_backup_date_separator_type date_separator,
+ char *timestamp, size_t len);
+
+/* Fetches a string representation of a backup
+ * list entry crc value.
+ * Returns false in the event of an error */
+bool core_backup_list_get_entry_crc_str(
+ const core_backup_list_entry_t *entry,
+ char *crc, size_t len);
+
+RETRO_END_DECLS
+
+#endif
diff --git a/core_updater_list.c b/core_updater_list.c
index 9e5e81d77f..3bd973cbb0 100644
--- a/core_updater_list.c
+++ b/core_updater_list.c
@@ -509,10 +509,8 @@ static bool core_updater_list_set_paths(
last_underscore = (char*)strrchr(local_info_path, '_');
if (!string_is_empty(last_underscore))
- {
- if (string_is_not_equal_fast(last_underscore, "_libretro", 9))
+ if (!string_is_equal(last_underscore, "_libretro"))
*last_underscore = '\0';
- }
/* > Add proper file extension */
strlcat(
diff --git a/file_path_special.h b/file_path_special.h
index 72bea45e19..72e8fda1aa 100644
--- a/file_path_special.h
+++ b/file_path_special.h
@@ -96,7 +96,9 @@ enum file_path_enum
FILE_PATH_RUNTIME_EXTENSION,
FILE_PATH_DEFAULT_EVENT_LOG,
FILE_PATH_EVENT_LOG_EXTENSION,
- FILE_PATH_DISK_CONTROL_INDEX_EXTENSION
+ FILE_PATH_DISK_CONTROL_INDEX_EXTENSION,
+ FILE_PATH_CORE_BACKUP_EXTENSION,
+ FILE_PATH_CORE_BACKUP_EXTENSION_NO_DOT
};
enum application_special_type
diff --git a/file_path_str.c b/file_path_str.c
index dc09b6f360..7e99ba7218 100644
--- a/file_path_str.c
+++ b/file_path_str.c
@@ -230,6 +230,12 @@ const char *file_path_str(enum file_path_enum enum_idx)
case FILE_PATH_DISK_CONTROL_INDEX_EXTENSION:
str = ".ldci";
break;
+ case FILE_PATH_CORE_BACKUP_EXTENSION:
+ str = ".lcbk";
+ break;
+ case FILE_PATH_CORE_BACKUP_EXTENSION_NO_DOT:
+ str = "lcbk";
+ break;
case FILE_PATH_UNKNOWN:
default:
break;
diff --git a/griffin/griffin.c b/griffin/griffin.c
index 7694e69b2d..5d015eaacf 100644
--- a/griffin/griffin.c
+++ b/griffin/griffin.c
@@ -1096,6 +1096,7 @@ FRONTEND
#endif
#include "../core_info.c"
+#include "../core_backup.c"
#if defined(HAVE_NETWORKING)
#include "../core_updater_list.c"
@@ -1242,6 +1243,7 @@ DATA RUNLOOP
#include "../tasks/task_file_transfer.c"
#include "../tasks/task_playlist_manager.c"
#include "../tasks/task_manual_content_scan.c"
+#include "../tasks/task_core_backup.c"
#ifdef HAVE_ZLIB
#include "../tasks/task_decompress.c"
#endif
@@ -1615,3 +1617,8 @@ DISK CONTROL INTERFACE
MISC FILE FORMATS
============================================================ */
#include "../libretro-common/formats/m3u/m3u_file.c"
+
+/*============================================================
+TIME
+============================================================ */
+#include "../libretro-common/time/rtime.c"
diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h
index a06480016f..04bc8b13fb 100644
--- a/intl/msg_hash_lbl.h
+++ b/intl/msg_hash_lbl.h
@@ -448,6 +448,26 @@ MSG_HASH(
MENU_ENUM_LABEL_CORE_INFORMATION,
"core_information"
)
+MSG_HASH(
+ MENU_ENUM_LABEL_CORE_CREATE_BACKUP,
+ "core_create_backup"
+ )
+MSG_HASH(
+ MENU_ENUM_LABEL_CORE_RESTORE_BACKUP_LIST,
+ "core_restore_backup_list"
+ )
+MSG_HASH(
+ MENU_ENUM_LABEL_DEFERRED_CORE_RESTORE_BACKUP_LIST,
+ "deferred_core_restore_backup_list"
+ )
+MSG_HASH(
+ MENU_ENUM_LABEL_CORE_DELETE_BACKUP_LIST,
+ "core_delete_backup_list"
+ )
+MSG_HASH(
+ MENU_ENUM_LABEL_DEFERRED_CORE_DELETE_BACKUP_LIST,
+ "deferred_core_delete_backup_list"
+ )
MSG_HASH(
MENU_ENUM_LABEL_DISC_INFORMATION,
"disc_information"
@@ -1790,6 +1810,10 @@ MSG_HASH(
MENU_ENUM_LABEL_NO_CORE_INFORMATION_AVAILABLE,
"no_core_information_available"
)
+MSG_HASH(
+ MENU_ENUM_LABEL_NO_CORE_BACKUPS_AVAILABLE,
+ "no_core_backups_available"
+ )
MSG_HASH(
MENU_ENUM_LABEL_NO_CORE_OPTIONS_AVAILABLE,
"no_core_options_available"
diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h
index d1cd3a42d9..c7e1aee643 100644
--- a/intl/msg_hash_us.h
+++ b/intl/msg_hash_us.h
@@ -457,6 +457,34 @@ MSG_HASH(
MENU_ENUM_SUBLABEL_CORE_DELETE,
"Remove this core from disk."
)
+MSG_HASH(
+ MENU_ENUM_LABEL_VALUE_CORE_CREATE_BACKUP,
+ "Backup Core"
+ )
+MSG_HASH(
+ MENU_ENUM_SUBLABEL_CORE_CREATE_BACKUP,
+ "Create an archived backup of the currently installed core."
+ )
+MSG_HASH(
+ MENU_ENUM_LABEL_VALUE_CORE_RESTORE_BACKUP_LIST,
+ "Restore Backup"
+ )
+MSG_HASH(
+ MENU_ENUM_SUBLABEL_CORE_RESTORE_BACKUP_LIST,
+ "Install a previous version of the core from a list of archived backups."
+ )
+MSG_HASH(
+ MENU_ENUM_LABEL_VALUE_CORE_DELETE_BACKUP_LIST,
+ "Delete Backup"
+ )
+MSG_HASH(
+ MENU_ENUM_SUBLABEL_CORE_DELETE_BACKUP_LIST,
+ "Remove a file from the list of archived backups."
+ )
+MSG_HASH(
+ MENU_ENUM_LABEL_VALUE_CORE_BACKUP_CRC,
+ "CRC32: "
+ )
/* Main Menu > Information > System Information */
@@ -6070,6 +6098,10 @@ MSG_HASH(
MENU_ENUM_LABEL_VALUE_NO_CORE_INFORMATION_AVAILABLE,
"No Core Information Available"
)
+MSG_HASH(
+ MENU_ENUM_LABEL_VALUE_NO_CORE_BACKUPS_AVAILABLE,
+ "No Core Backups Available"
+ )
MSG_HASH(
MENU_ENUM_LABEL_VALUE_NO_FAVORITES_AVAILABLE,
"No Favorites Available"
@@ -10604,6 +10636,62 @@ MSG_HASH(
MSG_MANUAL_CONTENT_SCAN_END,
"Scan complete: "
)
+MSG_HASH(
+ MSG_CORE_BACKUP_SCANNING_CORE,
+ "Scanning core: "
+ )
+MSG_HASH(
+ MSG_CORE_BACKUP_ALREADY_EXISTS,
+ "Backup of installed core already exists: "
+ )
+MSG_HASH(
+ MSG_BACKING_UP_CORE,
+ "Backing up core: "
+ )
+MSG_HASH(
+ MSG_CORE_BACKUP_COMPLETE,
+ "Core backup complete: "
+ )
+MSG_HASH(
+ MSG_CORE_RESTORATION_ALREADY_INSTALLED,
+ "Selected core backup is already installed: "
+ )
+MSG_HASH(
+ MSG_RESTORING_CORE,
+ "Restoring core: "
+ )
+MSG_HASH(
+ MSG_CORE_RESTORATION_COMPLETE,
+ "Core restoration complete: "
+ )
+MSG_HASH(
+ MSG_CORE_INSTALLATION_ALREADY_INSTALLED,
+ "Selected core file is already installed: "
+ )
+MSG_HASH(
+ MSG_INSTALLING_CORE,
+ "Installing core: "
+ )
+MSG_HASH(
+ MSG_CORE_INSTALLATION_COMPLETE,
+ "Core installation complete: "
+ )
+MSG_HASH(
+ MSG_CORE_RESTORATION_INVALID_CONTENT,
+ "Invalid core file selected: "
+ )
+MSG_HASH(
+ MSG_CORE_BACKUP_FAILED,
+ "Core backup failed: "
+ )
+MSG_HASH(
+ MSG_CORE_RESTORATION_FAILED,
+ "Core restoration failed: "
+ )
+MSG_HASH(
+ MSG_CORE_INSTALLATION_FAILED,
+ "Core installation failed: "
+ )
/* Lakka */
diff --git a/libretro-common/file/file_path.c b/libretro-common/file/file_path.c
index 62a3eceaf2..80c5c31995 100644
--- a/libretro-common/file/file_path.c
+++ b/libretro-common/file/file_path.c
@@ -32,6 +32,7 @@
#include
#include
#include
+#include
/* TODO: There are probably some unnecessary things on this huge include list now but I'm too afraid to touch it */
#ifdef __APPLE__
@@ -482,11 +483,13 @@ void fill_pathname_parent_dir(char *out_dir,
size_t fill_dated_filename(char *out_filename,
const char *ext, size_t size)
{
- time_t cur_time = time(NULL);
- const struct tm* tm_ = localtime(&cur_time);
+ time_t cur_time = time(NULL);
+ struct tm tm_;
+
+ rtime_localtime(&cur_time, &tm_);
strftime(out_filename, size,
- "RetroArch-%m%d-%H%M%S", tm_);
+ "RetroArch-%m%d-%H%M%S", &tm_);
return strlcat(out_filename, ext, size);
}
@@ -507,19 +510,21 @@ void fill_str_dated_filename(char *out_filename,
const char *in_str, const char *ext, size_t size)
{
char format[256];
- time_t cur_time = time(NULL);
- const struct tm* tm_ = localtime(&cur_time);
+ struct tm tm_;
+ time_t cur_time = time(NULL);
- format[0] = '\0';
+ format[0] = '\0';
+
+ rtime_localtime(&cur_time, &tm_);
if (string_is_empty(ext))
{
- strftime(format, sizeof(format), "-%y%m%d-%H%M%S", tm_);
+ strftime(format, sizeof(format), "-%y%m%d-%H%M%S", &tm_);
fill_pathname_noext(out_filename, in_str, format, size);
}
else
{
- strftime(format, sizeof(format), "-%y%m%d-%H%M%S.", tm_);
+ strftime(format, sizeof(format), "-%y%m%d-%H%M%S.", &tm_);
fill_pathname_join_concat_noext(out_filename,
in_str, format, ext,
diff --git a/libretro-common/include/streams/interface_stream.h b/libretro-common/include/streams/interface_stream.h
index 4ebc2f2389..2070cc0724 100644
--- a/libretro-common/include/streams/interface_stream.h
+++ b/libretro-common/include/streams/interface_stream.h
@@ -108,6 +108,8 @@ uint32_t intfstream_get_frame_size(intfstream_internal_t *intf);
bool intfstream_is_compressed(intfstream_internal_t *intf);
+bool intfstream_get_crc(intfstream_internal_t *intf, uint32_t *crc);
+
intfstream_t *intfstream_open_file(const char *path,
unsigned mode, unsigned hints);
diff --git a/libretro-common/include/time/rtime.h b/libretro-common/include/time/rtime.h
new file mode 100644
index 0000000000..7629c1eab3
--- /dev/null
+++ b/libretro-common/include/time/rtime.h
@@ -0,0 +1,48 @@
+/* Copyright (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (rtime.h).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __LIBRETRO_SDK_RTIME_H__
+#define __LIBRETRO_SDK_RTIME_H__
+
+#include
+
+#include
+#include
+#include
+
+RETRO_BEGIN_DECLS
+
+/* TODO/FIXME: Move all generic time handling functions
+ * to this file */
+
+/* Must be called before using rtime_localtime() */
+void rtime_init(void);
+
+/* Must be called upon program termination */
+void rtime_deinit(void);
+
+/* Thread-safe wrapper for localtime() */
+struct tm *rtime_localtime(const time_t *timep, struct tm *result);
+
+RETRO_END_DECLS
+
+#endif
diff --git a/libretro-common/streams/interface_stream.c b/libretro-common/streams/interface_stream.c
index ba933ced5c..65c4626610 100644
--- a/libretro-common/streams/interface_stream.c
+++ b/libretro-common/streams/interface_stream.c
@@ -31,6 +31,7 @@
#if defined(HAVE_ZLIB)
#include
#endif
+#include
struct intfstream_internal
{
@@ -615,6 +616,32 @@ bool intfstream_is_compressed(intfstream_internal_t *intf)
return false;
}
+bool intfstream_get_crc(intfstream_internal_t *intf, uint32_t *crc)
+{
+ int64_t data_read = 0;
+ uint32_t accumulator = 0;
+ uint8_t buffer[4096];
+
+ if (!intf || !crc)
+ return false;
+
+ /* Ensure we start at the beginning of the file */
+ intfstream_rewind(intf);
+
+ while ((data_read = intfstream_read(intf, buffer, sizeof(buffer))) > 0)
+ accumulator = encoding_crc32(accumulator, buffer, (size_t)data_read);
+
+ if (data_read < 0)
+ return false;
+
+ *crc = accumulator;
+
+ /* Reset file to the beginning */
+ intfstream_rewind(intf);
+
+ return true;
+}
+
intfstream_t* intfstream_open_file(const char *path,
unsigned mode, unsigned hints)
{
diff --git a/libretro-common/time/rtime.c b/libretro-common/time/rtime.c
new file mode 100644
index 0000000000..1195da2bb9
--- /dev/null
+++ b/libretro-common/time/rtime.c
@@ -0,0 +1,80 @@
+/* Copyright (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (rtime.c).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifdef HAVE_THREADS
+#include
+#include
+#include
+#endif
+
+#include
+#include
+
+#ifdef HAVE_THREADS
+slock_t *rtime_localtime_lock = NULL;
+#endif
+
+/* Must be called before using rtime_localtime() */
+void rtime_init(void)
+{
+ rtime_deinit();
+#ifdef HAVE_THREADS
+ if (!rtime_localtime_lock)
+ rtime_localtime_lock = slock_new();
+
+ retro_assert(rtime_localtime_lock);
+#endif
+}
+
+/* Must be called upon program termination */
+void rtime_deinit(void)
+{
+#ifdef HAVE_THREADS
+ if (rtime_localtime_lock)
+ {
+ slock_free(rtime_localtime_lock);
+ rtime_localtime_lock = NULL;
+ }
+#endif
+}
+
+/* Thread-safe wrapper for localtime() */
+struct tm *rtime_localtime(const time_t *timep, struct tm *result)
+{
+ struct tm *time_info = NULL;
+
+ /* Lock mutex */
+#ifdef HAVE_THREADS
+ slock_lock(rtime_localtime_lock);
+#endif
+
+ time_info = localtime(timep);
+ if (time_info)
+ memcpy(result, time_info, sizeof(struct tm));
+
+ /* Unlock mutex */
+#ifdef HAVE_THREADS
+ slock_unlock(rtime_localtime_lock);
+#endif
+
+ return result;
+}
diff --git a/menu/cbs/menu_cbs_deferred_push.c b/menu/cbs/menu_cbs_deferred_push.c
index e1962582cc..022f076713 100644
--- a/menu/cbs/menu_cbs_deferred_push.c
+++ b/menu/cbs/menu_cbs_deferred_push.c
@@ -255,6 +255,9 @@ generic_deferred_push(deferred_push_switch_backlight_control, DISPLAYLIST_
generic_deferred_push(deferred_push_manual_content_scan_list, DISPLAYLIST_MANUAL_CONTENT_SCAN_LIST)
generic_deferred_push(deferred_push_manual_content_scan_dat_file, DISPLAYLIST_MANUAL_CONTENT_SCAN_DAT_FILES)
+generic_deferred_push(deferred_push_core_restore_backup_list, DISPLAYLIST_CORE_RESTORE_BACKUP_LIST)
+generic_deferred_push(deferred_push_core_delete_backup_list, DISPLAYLIST_CORE_DELETE_BACKUP_LIST)
+
generic_deferred_push(deferred_push_file_browser_select_sideload_core, DISPLAYLIST_FILE_BROWSER_SELECT_SIDELOAD_CORE)
static int deferred_push_cursor_manager_list_deferred(
@@ -873,6 +876,8 @@ static int menu_cbs_init_bind_deferred_push_compare_label(
{MENU_ENUM_LABEL_FAVORITES, deferred_push_detect_core_list},
{MENU_ENUM_LABEL_DEFERRED_MANUAL_CONTENT_SCAN_LIST, deferred_push_manual_content_scan_list},
{MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_DAT_FILE, deferred_push_manual_content_scan_dat_file},
+ {MENU_ENUM_LABEL_DEFERRED_CORE_RESTORE_BACKUP_LIST, deferred_push_core_restore_backup_list},
+ {MENU_ENUM_LABEL_DEFERRED_CORE_DELETE_BACKUP_LIST, deferred_push_core_delete_backup_list},
{MENU_ENUM_LABEL_SIDELOAD_CORE_LIST, deferred_push_file_browser_select_sideload_core},
{MENU_ENUM_LABEL_DEFERRED_ARCHIVE_ACTION_DETECT_CORE, deferred_archive_action_detect_core},
{MENU_ENUM_LABEL_DEFERRED_ARCHIVE_ACTION, deferred_archive_action},
@@ -1266,6 +1271,12 @@ static int menu_cbs_init_bind_deferred_push_compare_label(
case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_DAT_FILE:
BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_manual_content_scan_dat_file);
break;
+ case MENU_ENUM_LABEL_DEFERRED_CORE_RESTORE_BACKUP_LIST:
+ BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_core_restore_backup_list);
+ break;
+ case MENU_ENUM_LABEL_DEFERRED_CORE_DELETE_BACKUP_LIST:
+ BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_core_delete_backup_list);
+ break;
case MENU_ENUM_LABEL_SIDELOAD_CORE_LIST:
BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_file_browser_select_sideload_core);
break;
diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c
index c70705120a..ca9e4f8ecf 100644
--- a/menu/cbs/menu_cbs_ok.c
+++ b/menu/cbs/menu_cbs_ok.c
@@ -302,6 +302,10 @@ static enum msg_hash_enums action_ok_dl_to_enum(unsigned lbl)
return MENU_ENUM_LABEL_DEFERRED_CORE_SETTINGS_LIST;
case ACTION_OK_DL_CORE_INFORMATION_LIST:
return MENU_ENUM_LABEL_DEFERRED_CORE_INFORMATION_LIST;
+ case ACTION_OK_DL_CORE_RESTORE_BACKUP_LIST:
+ return MENU_ENUM_LABEL_DEFERRED_CORE_RESTORE_BACKUP_LIST;
+ case ACTION_OK_DL_CORE_DELETE_BACKUP_LIST:
+ return MENU_ENUM_LABEL_DEFERRED_CORE_DELETE_BACKUP_LIST;
case ACTION_OK_DL_VIDEO_SETTINGS_LIST:
return MENU_ENUM_LABEL_DEFERRED_VIDEO_SETTINGS_LIST;
case ACTION_OK_DL_VIDEO_SYNCHRONIZATION_SETTINGS_LIST:
@@ -1269,6 +1273,8 @@ int generic_action_ok_displaylist_push(const char *path,
action_ok_dl_lbl(action_ok_dl_to_enum(action_type), DISPLAYLIST_GENERIC);
break;
case ACTION_OK_DL_CDROM_INFO_DETAIL_LIST:
+ case ACTION_OK_DL_CORE_RESTORE_BACKUP_LIST:
+ case ACTION_OK_DL_CORE_DELETE_BACKUP_LIST:
action_ok_dl_lbl(action_ok_dl_to_enum(action_type), DISPLAYLIST_GENERIC);
info_path = label;
break;
@@ -1632,88 +1638,6 @@ int generic_action_ok_command(enum event_command cmd)
return 0;
}
-/* TO-DO: Localization for errors */
-static bool file_copy(const char *src_path, const char *dst_path, char *msg, size_t size)
-{
- RFILE *src = NULL;
- RFILE *dst = NULL;
- bool ret = true;
-
- /* Sanity check */
- if (string_is_empty(src_path) || string_is_empty(dst_path))
- {
- strlcpy(msg, "invalid arguments", size);
- ret = false;
- goto end;
- }
-
- if (!path_is_valid(src_path))
- {
- strlcpy(msg, "source file does not exist", size);
- ret = false;
- goto end;
- }
-
- /* Open source file */
- src = filestream_open(
- src_path,
- RETRO_VFS_FILE_ACCESS_READ,
- RETRO_VFS_FILE_ACCESS_HINT_NONE);
-
- if (!src)
- {
- strlcpy(msg, "unable to open source file", size);
- ret = false;
- goto end;
- }
-
- /* Open destination file */
- dst = filestream_open(
- dst_path,
- RETRO_VFS_FILE_ACCESS_WRITE,
- RETRO_VFS_FILE_ACCESS_HINT_NONE);
-
- if (!dst)
- {
- strlcpy(msg, "unable to open destination file", size);
- ret = false;
- goto end;
- }
-
- /* Copy file contents */
- while (!filestream_eof(src))
- {
- int64_t numw;
- char buffer[100] = {0};
- int64_t numr = filestream_read(src, buffer, sizeof(buffer));
-
- if (filestream_error(dst) != 0)
- {
- strlcpy(msg, "error reading source file", size);
- ret = false;
- goto end;
- }
-
- numw = filestream_write(dst, buffer, numr);
-
- if (numw != numr)
- {
- strlcpy(msg, "error writing to destination file", size);
- ret = false;
- goto end;
- }
- }
-
-end:
- if (src)
- filestream_close(src);
-
- if (dst)
- filestream_close(dst);
-
- return ret;
-}
-
static int generic_action_ok(const char *path,
const char *label, unsigned type, size_t idx, size_t entry_idx,
unsigned id, enum msg_hash_enums flush_id)
@@ -4586,90 +4510,39 @@ static int action_ok_update_installed_cores(const char *path,
static int action_ok_sideload_core(const char *path,
const char *label, unsigned type, size_t idx, size_t entry_idx)
{
- char src_path[PATH_MAX_LENGTH];
- char dst_path[PATH_MAX_LENGTH];
- char msg[PATH_MAX_LENGTH];
+ char backup_path[PATH_MAX_LENGTH];
const char *menu_path = NULL;
const char *core_file = path;
- int ret = -1;
+ bool core_loaded = false;
menu_handle_t *menu = menu_driver_get_ptr();
settings_t *settings = config_get_ptr();
const char *dir_libretro = settings->paths.directory_libretro;
- src_path[0] = '\0';
- dst_path[0] = '\0';
- msg[0] = '\0';
+ backup_path[0] = '\0';
- /* Sanity check */
- if (!menu)
+ if (string_is_empty(core_file) || !menu)
return menu_cbs_exit();
- if (string_is_empty(core_file))
- goto end;
-
- if (string_is_empty(dir_libretro))
- goto end;
-
- /* Get source core path */
+ /* Get path of source (core 'backup') file */
menu_entries_get_last_stack(
&menu_path, NULL, NULL, NULL, NULL);
if (!string_is_empty(menu_path))
fill_pathname_join(
- src_path, menu_path, core_file, sizeof(src_path));
+ backup_path, menu_path, core_file, sizeof(backup_path));
else
- strlcpy(src_path, core_file, sizeof(src_path));
+ strlcpy(backup_path, core_file, sizeof(backup_path));
- /* Get destination core path */
- fill_pathname_join(
- dst_path, dir_libretro,
- core_file, sizeof(dst_path));
+ /* Push core 'restore' task */
+ task_push_core_restore(backup_path, dir_libretro, &core_loaded);
- /* Copy core file from source to destination */
- if (file_copy(src_path, dst_path, msg, sizeof(msg)))
- {
- /* Success */
-
- /* Reload core info files */
- command_event(CMD_EVENT_CORE_INFO_INIT, NULL);
-
- /* Log result */
- runloop_msg_queue_push(
- msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_SUCCESS),
- 1, 100, true,
- NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
-
- RARCH_LOG(
- "[sideload] %s\n",
- msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_SUCCESS));
- }
- else
- {
- /* Failure - just log result */
- runloop_msg_queue_push(
- msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_ERROR),
- 1, 100, true,
- NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
-
- RARCH_LOG(
- "[sideload] %s: %s\n",
- msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_ERROR), msg);
- }
-
- /* Regardless of file copy success/failure, function
- * should return zero if we get this far (since a
- * failure would correspond to a filesystem error,
- * not a menu error...) */
- ret = 0;
-
-end:
/* Flush stack
* > Since the 'sideload core' option is present
* in several locations, can't flush to a predefined
* level - just go to the top */
menu_entries_flush_stack(NULL, 0);
- return ret;
+ return 0;
}
default_action_ok_download(action_ok_core_content_thumbnails, MENU_ENUM_LABEL_CB_CORE_THUMBNAILS_DOWNLOAD)
@@ -5181,6 +5054,8 @@ default_action_ok_func(action_ok_push_video_output_settings_list, ACTION_OK_DL_V
default_action_ok_func(action_ok_push_configuration_settings_list, ACTION_OK_DL_CONFIGURATION_SETTINGS_LIST)
default_action_ok_func(action_ok_push_core_settings_list, ACTION_OK_DL_CORE_SETTINGS_LIST)
default_action_ok_func(action_ok_push_core_information_list, ACTION_OK_DL_CORE_INFORMATION_LIST)
+default_action_ok_func(action_ok_push_core_restore_backup_list, ACTION_OK_DL_CORE_RESTORE_BACKUP_LIST)
+default_action_ok_func(action_ok_push_core_delete_backup_list, ACTION_OK_DL_CORE_DELETE_BACKUP_LIST)
default_action_ok_func(action_ok_push_audio_settings_list, ACTION_OK_DL_AUDIO_SETTINGS_LIST)
default_action_ok_func(action_ok_push_audio_output_settings_list, ACTION_OK_DL_AUDIO_OUTPUT_SETTINGS_LIST)
default_action_ok_func(action_ok_push_audio_resampler_settings_list, ACTION_OK_DL_AUDIO_RESAMPLER_SETTINGS_LIST)
@@ -6477,13 +6352,73 @@ static int action_ok_netplay_disconnect(const char *path,
#endif
}
+static int action_ok_core_create_backup(const char *path,
+ const char *label, unsigned type, size_t idx, size_t entry_idx)
+{
+ const char *core_path = label;
+ settings_t *settings = config_get_ptr();
+ const char *dir_core_assets = settings->paths.directory_core_assets;
+
+ if (string_is_empty(core_path))
+ return -1;
+
+ task_push_core_backup(core_path, 0, CORE_BACKUP_MODE_MANUAL,
+ dir_core_assets, false);
+
+ return 0;
+}
+
+static int action_ok_core_restore_backup(const char *path,
+ const char *label, unsigned type, size_t idx, size_t entry_idx)
+{
+ const char *backup_path = label;
+ bool core_loaded = false;
+ settings_t *settings = config_get_ptr();
+ const char *dir_libretro = settings->paths.directory_libretro;
+
+ if (string_is_empty(backup_path))
+ return -1;
+
+ /* If core to be restored is currently loaded, the task
+ * will unload it
+ * > In this case, must flush the menu stack
+ * (otherwise user will be faced with 'no information
+ * available' when popping the stack - this would be
+ * confusing/ugly) */
+ if (task_push_core_restore(backup_path, dir_libretro, &core_loaded) &&
+ core_loaded)
+ menu_entries_flush_stack(NULL, 0);
+
+ return 0;
+}
+
+static int action_ok_core_delete_backup(const char *path,
+ const char *label, unsigned type, size_t idx, size_t entry_idx)
+{
+ const char *backup_path = label;
+ bool refresh = false;
+
+ if (string_is_empty(backup_path))
+ return -1;
+
+ /* Delete backup file (if it exists) */
+ if (path_is_valid(backup_path))
+ filestream_delete(backup_path);
+
+ /* Refresh menu */
+ menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh);
+ menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL);
+
+ return 0;
+}
+
static int action_ok_core_delete(const char *path,
const char *label, unsigned type, size_t idx, size_t entry_idx)
{
const char *core_path = label;
const char *core = NULL;
- const char *running_core_path = NULL;
- const char *running_core = NULL;
+ const char *loaded_core_path = NULL;
+ const char *loaded_core = NULL;
if (string_is_empty(core_path))
return -1;
@@ -6493,15 +6428,15 @@ static int action_ok_core_delete(const char *path,
if (string_is_empty(core))
return -1;
- /* Get running core file name */
- running_core_path = path_get(RARCH_PATH_CORE);
- if (!string_is_empty(running_core_path))
- running_core = path_basename(running_core_path);
+ /* Get loaded core file name */
+ loaded_core_path = path_get(RARCH_PATH_CORE);
+ if (!string_is_empty(loaded_core_path))
+ loaded_core = path_basename(loaded_core_path);
/* Check if core to be deleted is currently
- * running - if so, unload it */
- if (!string_is_empty(running_core) &&
- string_is_equal(core, running_core))
+ * loaded - if so, unload it */
+ if (!string_is_empty(loaded_core) &&
+ string_is_equal(core, loaded_core))
generic_action_ok_command(CMD_EVENT_UNLOAD_CORE);
/* Delete core file */
@@ -6867,6 +6802,7 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs,
{MENU_ENUM_LABEL_NETPLAY_ENABLE_CLIENT, action_ok_netplay_enable_client},
{MENU_ENUM_LABEL_NETPLAY_DISCONNECT, action_ok_netplay_disconnect},
{MENU_ENUM_LABEL_CORE_DELETE, action_ok_core_delete},
+ {MENU_ENUM_LABEL_CORE_CREATE_BACKUP, action_ok_core_create_backup},
{MENU_ENUM_LABEL_DELETE_PLAYLIST, action_ok_delete_playlist},
{MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE, action_ok_cheevos_toggle_hardcore_mode},
{MENU_ENUM_LABEL_ACHIEVEMENT_RESUME, action_ok_cheevos_toggle_hardcore_mode},
@@ -6876,6 +6812,8 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs,
{MENU_ENUM_LABEL_LATENCY_SETTINGS, action_ok_push_latency_settings_list},
{MENU_ENUM_LABEL_CORE_SETTINGS, action_ok_push_core_settings_list},
{MENU_ENUM_LABEL_CORE_INFORMATION, action_ok_push_core_information_list},
+ {MENU_ENUM_LABEL_CORE_RESTORE_BACKUP_LIST, action_ok_push_core_restore_backup_list},
+ {MENU_ENUM_LABEL_CORE_DELETE_BACKUP_LIST, action_ok_push_core_delete_backup_list},
{MENU_ENUM_LABEL_CONFIGURATION_SETTINGS, action_ok_push_configuration_settings_list},
{MENU_ENUM_LABEL_PLAYLIST_SETTINGS, action_ok_push_playlist_settings_list},
{MENU_ENUM_LABEL_PLAYLIST_MANAGER_LIST, action_ok_push_playlist_manager_list},
@@ -7513,6 +7451,12 @@ static int menu_cbs_init_bind_ok_compare_type(menu_file_list_cbs_t *cbs,
case MENU_SETTINGS_CORE_OPTION_CREATE:
BIND_ACTION_OK(cbs, action_ok_option_create);
break;
+ case MENU_SETTING_ITEM_CORE_RESTORE_BACKUP:
+ BIND_ACTION_OK(cbs, action_ok_core_restore_backup);
+ break;
+ case MENU_SETTING_ITEM_CORE_DELETE_BACKUP:
+ BIND_ACTION_OK(cbs, action_ok_core_delete_backup);
+ break;
default:
return -1;
}
diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c
index de7b20ffc1..b9fdaec101 100644
--- a/menu/cbs/menu_cbs_sublabel.c
+++ b/menu/cbs/menu_cbs_sublabel.c
@@ -785,6 +785,9 @@ default_sublabel_macro(action_bind_sublabel_manual_content_scan_dat_file,
default_sublabel_macro(action_bind_sublabel_manual_content_scan_dat_file_filter, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_DAT_FILE_FILTER)
default_sublabel_macro(action_bind_sublabel_manual_content_scan_overwrite, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_OVERWRITE)
default_sublabel_macro(action_bind_sublabel_manual_content_scan_start, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_START)
+default_sublabel_macro(action_bind_sublabel_core_create_backup, MENU_ENUM_SUBLABEL_CORE_CREATE_BACKUP)
+default_sublabel_macro(action_bind_sublabel_core_restore_backup_list, MENU_ENUM_SUBLABEL_CORE_RESTORE_BACKUP_LIST)
+default_sublabel_macro(action_bind_sublabel_core_delete_backup_list, MENU_ENUM_SUBLABEL_CORE_DELETE_BACKUP_LIST)
static int action_bind_sublabel_systeminfo_controller_entry(
file_list_t *list,
@@ -1253,6 +1256,27 @@ static int action_bind_sublabel_core_updater_entry(
}
#endif
+static int action_bind_sublabel_core_backup_entry(
+ file_list_t *list,
+ unsigned type, unsigned i,
+ const char *label, const char *path,
+ char *s, size_t len)
+{
+ const char *crc = NULL;
+
+ /* crc is entered as 'alt' text */
+ menu_entries_get_at_offset(list, i, NULL,
+ NULL, NULL, NULL, &crc);
+
+ /* Set sublabel prefix */
+ strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_BACKUP_CRC), len);
+
+ /* Add crc string */
+ strlcat(s, (string_is_empty(crc) ? "00000000" : crc), len);
+
+ return 1;
+}
+
static int action_bind_sublabel_generic(
file_list_t *list,
unsigned type, unsigned i,
@@ -3426,6 +3450,19 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_START:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_manual_content_scan_start);
break;
+ case MENU_ENUM_LABEL_CORE_CREATE_BACKUP:
+ BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_core_create_backup);
+ break;
+ case MENU_ENUM_LABEL_CORE_RESTORE_BACKUP_LIST:
+ BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_core_restore_backup_list);
+ break;
+ case MENU_ENUM_LABEL_CORE_DELETE_BACKUP_LIST:
+ BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_core_delete_backup_list);
+ break;
+ case MENU_ENUM_LABEL_CORE_RESTORE_BACKUP_ENTRY:
+ case MENU_ENUM_LABEL_CORE_DELETE_BACKUP_ENTRY:
+ BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_core_backup_entry);
+ break;
default:
case MSG_UNKNOWN:
return -1;
diff --git a/menu/cbs/menu_cbs_title.c b/menu/cbs/menu_cbs_title.c
index 1692d75dae..fd5f00832e 100644
--- a/menu/cbs/menu_cbs_title.c
+++ b/menu/cbs/menu_cbs_title.c
@@ -25,6 +25,7 @@
#include "../../retroarch.h"
#include "../../configuration.h"
#include "../../managers/core_option_manager.h"
+#include "../../core_info.h"
#ifndef BIND_ACTION_GET_TITLE
#define BIND_ACTION_GET_TITLE(cbs, name) (cbs)->action_get_title = (name)
@@ -329,6 +330,54 @@ static int action_get_title_deferred_playlist_list(const char *path, const char
return 0;
}
+static int action_get_title_deferred_core_backup_list(
+ const char *core_path, const char *prefix, char *s, size_t len)
+{
+ core_info_ctx_find_t core_info;
+
+ if (string_is_empty(core_path) || string_is_empty(prefix))
+ return 0;
+
+ /* Set title prefix */
+ strlcpy(s, prefix, len);
+ strlcat(s, ": ", len);
+
+ /* Search for specified core */
+ core_info.inf = NULL;
+ core_info.path = core_path;
+
+ /* If core is found, add display name */
+ if (core_info_find(&core_info, core_path) &&
+ core_info.inf->display_name)
+ strlcat(s, core_info.inf->display_name, len);
+ else
+ {
+ /* If not, use core file name */
+ const char *core_filename = path_basename(core_path);
+
+ if (!string_is_empty(core_filename))
+ strlcat(s, core_filename, len);
+ }
+
+ return 1;
+}
+
+static int action_get_title_deferred_core_restore_backup_list(
+ const char *path, const char *label, unsigned menu_type, char *s, size_t len)
+{
+ return action_get_title_deferred_core_backup_list(path,
+ msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_RESTORE_BACKUP_LIST),
+ s, len);
+}
+
+static int action_get_title_deferred_core_delete_backup_list(
+ const char *path, const char *label, unsigned menu_type, char *s, size_t len)
+{
+ return action_get_title_deferred_core_backup_list(path,
+ msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_DELETE_BACKUP_LIST),
+ s, len);
+}
+
default_title_macro(action_get_quick_menu_override_options, MENU_ENUM_LABEL_VALUE_QUICK_MENU_OVERRIDE_OPTIONS)
default_title_macro(action_get_user_accounts_cheevos_list, MENU_ENUM_LABEL_VALUE_ACCOUNTS_RETRO_ACHIEVEMENTS)
default_title_macro(action_get_user_accounts_youtube_list, MENU_ENUM_LABEL_VALUE_ACCOUNTS_YOUTUBE)
@@ -663,6 +712,8 @@ static int menu_cbs_init_bind_title_compare_label(menu_file_list_cbs_t *cbs,
{MENU_ENUM_LABEL_DEFERRED_REMAPPINGS_PORT_LIST, action_get_title_remap_port},
{MENU_ENUM_LABEL_DEFERRED_CORE_SETTINGS_LIST, action_get_core_settings_list},
{MENU_ENUM_LABEL_DEFERRED_CORE_INFORMATION_LIST, action_get_core_information_list},
+ {MENU_ENUM_LABEL_DEFERRED_CORE_RESTORE_BACKUP_LIST, action_get_title_deferred_core_restore_backup_list},
+ {MENU_ENUM_LABEL_DEFERRED_CORE_DELETE_BACKUP_LIST, action_get_title_deferred_core_delete_backup_list},
{MENU_ENUM_LABEL_DEFERRED_DUMP_DISC_LIST, action_get_dump_disc_list},
{MENU_ENUM_LABEL_DEFERRED_LOAD_DISC_LIST, action_get_load_disc_list},
{MENU_ENUM_LABEL_DEFERRED_CONFIGURATION_SETTINGS_LIST, action_get_configuration_settings_list },
@@ -1217,6 +1268,12 @@ static int menu_cbs_init_bind_title_compare_label(menu_file_list_cbs_t *cbs,
case MENU_ENUM_LABEL_DEFERRED_CORE_INFORMATION_LIST:
BIND_ACTION_GET_TITLE(cbs, action_get_core_information_list);
break;
+ case MENU_ENUM_LABEL_DEFERRED_CORE_RESTORE_BACKUP_LIST:
+ BIND_ACTION_GET_TITLE(cbs, action_get_title_deferred_core_restore_backup_list);
+ break;
+ case MENU_ENUM_LABEL_DEFERRED_CORE_DELETE_BACKUP_LIST:
+ BIND_ACTION_GET_TITLE(cbs, action_get_title_deferred_core_delete_backup_list);
+ break;
case MENU_ENUM_LABEL_DEFERRED_INPUT_SETTINGS_LIST:
BIND_ACTION_GET_TITLE(cbs, action_get_input_settings_list);
break;
diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c
index 99757af03f..f6cbc95482 100644
--- a/menu/drivers/materialui.c
+++ b/menu/drivers/materialui.c
@@ -9157,9 +9157,18 @@ static void materialui_list_insert(
node->has_icon = true;
break;
case MENU_SETTING_ACTION_CORE_DELETE:
+ case MENU_SETTING_ACTION_CORE_DELETE_BACKUP:
node->icon_texture_index = MUI_TEXTURE_REMOVE;
node->has_icon = true;
break;
+ case MENU_SETTING_ACTION_CORE_CREATE_BACKUP:
+ node->icon_texture_index = MUI_TEXTURE_SAVE_STATE;
+ node->has_icon = true;
+ break;
+ case MENU_SETTING_ACTION_CORE_RESTORE_BACKUP:
+ node->icon_texture_index = MUI_TEXTURE_LOAD_STATE;
+ node->has_icon = true;
+ break;
default:
if (
string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_INFORMATION_LIST)) ||
@@ -9300,8 +9309,7 @@ static void materialui_list_insert(
node->icon_texture_index = MUI_TEXTURE_START_CORE;
node->has_icon = true;
}
- else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_LOAD_STATE))
- )
+ else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_LOAD_STATE)))
{
node->icon_texture_index = MUI_TEXTURE_LOAD_STATE;
node->has_icon = true;
@@ -9326,15 +9334,12 @@ static void materialui_list_insert(
node->icon_texture_index = MUI_TEXTURE_DISK;
node->has_icon = true;
}
- else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_SAVE_STATE))
- ||
- (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_CORE)))
- ||
- (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_CONTENT_DIR)))
- ||
- (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_QUICK_MENU_OVERRIDE_OPTIONS)))
- ||
- (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_GAME)))
+ else if (
+ string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_SAVE_STATE)) ||
+ string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_CORE)) ||
+ string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_CONTENT_DIR)) ||
+ string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_QUICK_MENU_OVERRIDE_OPTIONS)) ||
+ string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_GAME))
)
{
node->icon_texture_index = MUI_TEXTURE_SAVE_STATE;
diff --git a/menu/drivers/ozone/ozone_texture.c b/menu/drivers/ozone/ozone_texture.c
index e258632e04..4339206cfa 100644
--- a/menu/drivers/ozone/ozone_texture.c
+++ b/menu/drivers/ozone/ozone_texture.c
@@ -72,8 +72,10 @@ uintptr_t ozone_entries_icon_get_texture(ozone_handle_t *ozone,
case MENU_ENUM_LABEL_ACHIEVEMENT_LIST_HARDCORE:
return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_ACHIEVEMENT_LIST];
case MENU_ENUM_LABEL_SAVE_STATE:
+ case MENU_ENUM_LABEL_CORE_CREATE_BACKUP:
return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SAVESTATE];
case MENU_ENUM_LABEL_LOAD_STATE:
+ case MENU_ENUM_LABEL_CORE_RESTORE_BACKUP_LIST:
return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_LOADSTATE];
case MENU_ENUM_LABEL_PARENT_DIRECTORY:
case MENU_ENUM_LABEL_UNDO_LOAD_STATE:
@@ -234,6 +236,7 @@ uintptr_t ozone_entries_icon_get_texture(ozone_handle_t *ozone,
case MENU_ENUM_LABEL_REMAP_FILE_REMOVE_CONTENT_DIR:
case MENU_ENUM_LABEL_CORE_DELETE:
case MENU_ENUM_LABEL_DELETE_PLAYLIST:
+ case MENU_ENUM_LABEL_CORE_DELETE_BACKUP_LIST:
return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CLOSE];
case MENU_ENUM_LABEL_ONSCREEN_DISPLAY_SETTINGS:
return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_OSD];
diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c
index 28124ce1de..d0dcb3d9b8 100644
--- a/menu/drivers/xmb.c
+++ b/menu/drivers/xmb.c
@@ -2454,6 +2454,7 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb,
return xmb->textures.list[XMB_TEXTURE_ACHIEVEMENT_LIST];
case MENU_ENUM_LABEL_SAVE_STATE:
case MENU_ENUM_LABEL_SAVESTATE_AUTO_SAVE:
+ case MENU_ENUM_LABEL_CORE_CREATE_BACKUP:
return xmb->textures.list[XMB_TEXTURE_SAVESTATE];
case MENU_ENUM_LABEL_LOAD_STATE:
case MENU_ENUM_LABEL_CONFIGURATIONS:
@@ -2465,6 +2466,7 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb,
case MENU_ENUM_LABEL_CHEAT_FILE_LOAD:
case MENU_ENUM_LABEL_CHEAT_FILE_LOAD_APPEND:
case MENU_ENUM_LABEL_SAVESTATE_AUTO_LOAD:
+ case MENU_ENUM_LABEL_CORE_RESTORE_BACKUP_LIST:
return xmb->textures.list[XMB_TEXTURE_LOADSTATE];
case MENU_ENUM_LABEL_TAKE_SCREENSHOT:
return xmb->textures.list[XMB_TEXTURE_SCREENSHOT];
@@ -2638,6 +2640,7 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb,
case MENU_ENUM_LABEL_REMAP_FILE_REMOVE_CONTENT_DIR:
case MENU_ENUM_LABEL_CORE_DELETE:
case MENU_ENUM_LABEL_DELETE_PLAYLIST:
+ case MENU_ENUM_LABEL_CORE_DELETE_BACKUP_LIST:
return xmb->textures.list[XMB_TEXTURE_CLOSE];
case MENU_ENUM_LABEL_ONSCREEN_DISPLAY_SETTINGS:
return xmb->textures.list[XMB_TEXTURE_OSD];
diff --git a/menu/menu_cbs.h b/menu/menu_cbs.h
index 2bf9e73ae4..84bb8cda8d 100644
--- a/menu/menu_cbs.h
+++ b/menu/menu_cbs.h
@@ -191,7 +191,9 @@ enum
ACTION_OK_DL_CDROM_INFO_DETAIL_LIST,
ACTION_OK_DL_RGUI_MENU_THEME_PRESET,
ACTION_OK_DL_MANUAL_CONTENT_SCAN_LIST,
- ACTION_OK_DL_MANUAL_CONTENT_SCAN_DAT_FILE
+ ACTION_OK_DL_MANUAL_CONTENT_SCAN_DAT_FILE,
+ ACTION_OK_DL_CORE_RESTORE_BACKUP_LIST,
+ ACTION_OK_DL_CORE_DELETE_BACKUP_LIST
};
/* Function callbacks */
diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c
index 61b596513b..900149e9d5 100644
--- a/menu/menu_displaylist.c
+++ b/menu/menu_displaylist.c
@@ -100,6 +100,7 @@
#include "../dynamic.h"
#include "../runtime_file.h"
#include "../manual_content_scan.h"
+#include "../core_backup.h"
#define menu_displaylist_parse_settings_enum(list, label, parse_type, add_empty_entry) menu_displaylist_parse_settings_internal_enum(list, parse_type, add_empty_entry, menu_setting_find_enum(label), label, true)
@@ -159,9 +160,8 @@ static int menu_displaylist_parse_core_info(menu_displaylist_info_t *info)
if (core_info_find(&core_info_finder, core_path))
core_info = core_info_finder.inf;
}
- else
- if (core_info_get_current_core(&core_info))
- core_path = core_info->path;
+ else if (core_info_get_current_core(&core_info) && core_info)
+ core_path = core_info->path;
if (!core_info || !core_info->config_data)
{
@@ -172,18 +172,7 @@ static int menu_displaylist_parse_core_info(menu_displaylist_info_t *info)
0, 0, 0))
count++;
- if (menu_show_core_updater &&
- !string_is_empty(core_path))
- {
- if (menu_entries_append_enum(info->list,
- msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_DELETE),
- core_path,
- MENU_ENUM_LABEL_CORE_DELETE,
- MENU_SETTING_ACTION_CORE_DELETE, 0, 0))
- count++;
- }
-
- return count;
+ goto end;
}
{
@@ -367,11 +356,38 @@ static int menu_displaylist_parse_core_info(menu_displaylist_info_t *info)
}
}
+end:
+
#if defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
#else
if (menu_show_core_updater &&
!string_is_empty(core_path))
{
+ /* Backup core */
+ if (menu_entries_append_enum(info->list,
+ msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_CREATE_BACKUP),
+ core_path,
+ MENU_ENUM_LABEL_CORE_CREATE_BACKUP,
+ MENU_SETTING_ACTION_CORE_CREATE_BACKUP, 0, 0))
+ count++;
+
+ /* Restore core from backup */
+ if (menu_entries_append_enum(info->list,
+ msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_RESTORE_BACKUP_LIST),
+ core_path,
+ MENU_ENUM_LABEL_CORE_RESTORE_BACKUP_LIST,
+ MENU_SETTING_ACTION_CORE_RESTORE_BACKUP, 0, 0))
+ count++;
+
+ /* Delete core backup */
+ if (menu_entries_append_enum(info->list,
+ msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_DELETE_BACKUP_LIST),
+ core_path,
+ MENU_ENUM_LABEL_CORE_DELETE_BACKUP_LIST,
+ MENU_SETTING_ACTION_CORE_DELETE_BACKUP, 0, 0))
+ count++;
+
+ /* Delete core */
if (menu_entries_append_enum(info->list,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_DELETE),
core_path,
@@ -384,6 +400,95 @@ static int menu_displaylist_parse_core_info(menu_displaylist_info_t *info)
return count;
}
+static unsigned menu_displaylist_parse_core_backup_list(
+ menu_displaylist_info_t *info, bool restore)
+{
+ enum msg_hash_enums enum_idx;
+ enum menu_settings_type settings_type;
+ unsigned count = 0;
+ const char *core_path = info->path;
+ core_backup_list_t *backup_list = NULL;
+ settings_t *settings = config_get_ptr();
+ const char *dir_core_assets = settings->paths.directory_core_assets;
+ enum core_backup_date_separator_type
+ date_separator = (enum core_backup_date_separator_type)
+ settings->uints.menu_timedate_date_separator;
+
+ if (restore)
+ {
+ enum_idx = MENU_ENUM_LABEL_CORE_RESTORE_BACKUP_ENTRY;
+ settings_type = MENU_SETTING_ITEM_CORE_RESTORE_BACKUP;
+ }
+ else
+ {
+ /* If we're not restoring, we're deleting */
+ enum_idx = MENU_ENUM_LABEL_CORE_DELETE_BACKUP_ENTRY;
+ settings_type = MENU_SETTING_ITEM_CORE_DELETE_BACKUP;
+ }
+
+ /* Get backup list */
+ backup_list = core_backup_list_init(core_path, dir_core_assets);
+
+ if (backup_list)
+ {
+ size_t i;
+ size_t menu_index = 0;
+
+ for (i = 0; i < core_backup_list_size(backup_list); i++)
+ {
+ const core_backup_list_entry_t *entry = NULL;
+
+ /* Ensure entry is valid */
+ if (core_backup_list_get_index(backup_list, i, &entry) &&
+ entry && !string_is_empty(entry->backup_path))
+ {
+ char timestamp[32];
+ char crc[16];
+
+ timestamp[0] = '\0';
+ crc[0] = '\0';
+
+ /* Get timestamp and crc strings */
+ core_backup_list_get_entry_timestamp_str(
+ entry, date_separator, timestamp, sizeof(timestamp));
+ core_backup_list_get_entry_crc_str(
+ entry, crc, sizeof(crc));
+
+ /* Add menu entry */
+ if (menu_entries_append_enum(info->list,
+ timestamp,
+ entry->backup_path,
+ enum_idx,
+ settings_type, 0, 0))
+ {
+ /* We need to set backup path, timestamp and crc
+ * > Only have 2 useable fields as standard
+ * ('path' and 'label'), so have to set the
+ * crc as 'alt' text */
+ file_list_set_alt_at_offset(
+ info->list, menu_index, crc);
+
+ menu_index++;
+ count++;
+ }
+ }
+ }
+
+ core_backup_list_free(backup_list);
+ }
+
+ /* Fallback, in case no backups are found */
+ if (count == 0)
+ if (menu_entries_append_enum(info->list,
+ msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_CORE_BACKUPS_AVAILABLE),
+ msg_hash_to_str(MENU_ENUM_LABEL_NO_CORE_BACKUPS_AVAILABLE),
+ MENU_ENUM_LABEL_NO_CORE_BACKUPS_AVAILABLE,
+ 0, 0, 0))
+ count++;
+
+ return count;
+}
+
static unsigned menu_displaylist_parse_system_info(file_list_t *list)
{
int controller;
@@ -9400,9 +9505,41 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type,
}
break;
case DISPLAYLIST_CORE_INFO:
+ {
+ /* There is a (infinitesimally small) chance that
+ * the number of items in the core info menu will
+ * change after performing a core restore operation
+ * (i.e. the core info files are reloaded, and if
+ * an unknown error occurs then info entries may
+ * not be available upon popping the stack). We
+ * therefore have to cache the last set menu size,
+ * and reset the navigation pointer if the current
+ * size is different */
+ static size_t prev_count = 0;
+ menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list);
+ count = menu_displaylist_parse_core_info(info);
+
+ if (count != prev_count)
+ {
+ info->need_refresh = true;
+ info->need_navigation_clear = true;
+ prev_count = count;
+ }
+ info->need_push = true;
+ }
+ break;
+ case DISPLAYLIST_CORE_RESTORE_BACKUP_LIST:
menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list);
- count = menu_displaylist_parse_core_info(info);
- info->need_push = true;
+ count = menu_displaylist_parse_core_backup_list(info, true);
+ info->need_refresh = true;
+ info->need_push = true;
+ break;
+ case DISPLAYLIST_CORE_DELETE_BACKUP_LIST:
+ menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list);
+ count = menu_displaylist_parse_core_backup_list(info, false);
+ info->need_navigation_clear = true;
+ info->need_refresh = true;
+ info->need_push = true;
break;
case DISPLAYLIST_CORE_OPTIONS:
{
@@ -10503,14 +10640,20 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type,
break;
case DISPLAYLIST_FILE_BROWSER_SELECT_SIDELOAD_CORE:
{
- char ext_name[PATH_MAX_LENGTH];
- ext_name[0] = '\0';
+ char ext_names[255];
+ ext_names[0] = '\0';
info->type_default = FILE_TYPE_SIDELOAD_CORE;
- if (frontend_driver_get_core_extension(
- ext_name, sizeof(ext_name)))
- info->exts = strdup(ext_name);
+ if (frontend_driver_get_core_extension(ext_names, sizeof(ext_names)))
+ {
+ strlcat(ext_names, "|", sizeof(ext_names));
+ strlcat(ext_names, file_path_str(FILE_PATH_CORE_BACKUP_EXTENSION_NO_DOT), sizeof(ext_names));
+ }
+ else
+ strlcpy(ext_names, file_path_str(FILE_PATH_CORE_BACKUP_EXTENSION_NO_DOT), sizeof(ext_names));
+
+ info->exts = strdup(ext_names);
}
break;
default:
diff --git a/menu/menu_displaylist.h b/menu/menu_displaylist.h
index 6ced2b8328..df94497b1d 100644
--- a/menu/menu_displaylist.h
+++ b/menu/menu_displaylist.h
@@ -235,6 +235,8 @@ enum menu_displaylist_ctl_state
#endif
DISPLAYLIST_MANUAL_CONTENT_SCAN_LIST,
DISPLAYLIST_MANUAL_CONTENT_SCAN_DAT_FILES,
+ DISPLAYLIST_CORE_RESTORE_BACKUP_LIST,
+ DISPLAYLIST_CORE_DELETE_BACKUP_LIST,
DISPLAYLIST_PENDING_CLEAR
};
diff --git a/menu/menu_driver.c b/menu/menu_driver.c
index 389d25f948..fd3d499087 100644
--- a/menu/menu_driver.c
+++ b/menu/menu_driver.c
@@ -28,6 +28,7 @@
#include
#include
#include
+#include
#ifdef HAVE_CONFIG_H
#include "../config.h"
@@ -2259,6 +2260,10 @@ static void strftime_am_pm(char* ptr, size_t maxsize, const char* format,
{
char *local = NULL;
+ /* Ensure correct locale is set
+ * > Required for localised AM/PM strings */
+ setlocale(LC_TIME, "");
+
#if defined(__linux__) && !defined(ANDROID)
strftime(ptr, maxsize, format, timeptr);
#else
@@ -2276,7 +2281,6 @@ static void strftime_am_pm(char* ptr, size_t maxsize, const char* format,
#endif
}
-
/* Display the date and time - time_mode will influence how
* the time representation will look like.
* */
@@ -2291,7 +2295,7 @@ void menu_display_timedate(gfx_display_ctx_datetime_t *datetime)
DATETIME_CHECK_INTERVAL)
{
time_t time_;
- const struct tm *tm_;
+ struct tm tm_;
bool has_am_pm = false;
const char *format_str = "";
@@ -2299,10 +2303,7 @@ void menu_display_timedate(gfx_display_ctx_datetime_t *datetime)
/* Get current time */
time(&time_);
-
- setlocale(LC_TIME, "");
-
- tm_ = localtime(&time_);
+ rtime_localtime(&time_, &tm_);
/* Format string representation */
switch (datetime->time_mode)
@@ -2645,10 +2646,10 @@ void menu_display_timedate(gfx_display_ctx_datetime_t *datetime)
if (has_am_pm)
strftime_am_pm(menu_st->datetime_cache, sizeof(menu_st->datetime_cache),
- format_str, tm_);
+ format_str, &tm_);
else
strftime(menu_st->datetime_cache, sizeof(menu_st->datetime_cache),
- format_str, tm_);
+ format_str, &tm_);
}
/* Copy cached datetime string to input
diff --git a/menu/menu_driver.h b/menu/menu_driver.h
index 3b7cce2891..80e4be08dc 100644
--- a/menu/menu_driver.h
+++ b/menu/menu_driver.h
@@ -205,6 +205,12 @@ enum menu_settings_type
MENU_SETTING_MANUAL_CONTENT_SCAN_CORE_NAME,
MENU_SETTING_ACTION_MANUAL_CONTENT_SCAN_START,
+ MENU_SETTING_ACTION_CORE_CREATE_BACKUP,
+ MENU_SETTING_ACTION_CORE_RESTORE_BACKUP,
+ MENU_SETTING_ITEM_CORE_RESTORE_BACKUP,
+ MENU_SETTING_ACTION_CORE_DELETE_BACKUP,
+ MENU_SETTING_ITEM_CORE_DELETE_BACKUP,
+
MENU_SETTINGS_LAST
};
diff --git a/msg_hash.h b/msg_hash.h
index 46eebd94ed..8e52b41597 100644
--- a/msg_hash.h
+++ b/msg_hash.h
@@ -1392,6 +1392,8 @@ enum msg_hash_enums
MENU_ENUM_LABEL_DEFERRED_AUDIO_MIXER_SETTINGS_LIST,
MENU_ENUM_LABEL_DEFERRED_CORE_SETTINGS_LIST,
MENU_ENUM_LABEL_DEFERRED_CORE_INFORMATION_LIST,
+ MENU_ENUM_LABEL_DEFERRED_CORE_RESTORE_BACKUP_LIST,
+ MENU_ENUM_LABEL_DEFERRED_CORE_DELETE_BACKUP_LIST,
MENU_ENUM_LABEL_DEFERRED_USER_BINDS_LIST,
MENU_ENUM_LABEL_DEFERRED_ACCOUNTS_CHEEVOS_LIST,
MENU_ENUM_LABEL_DEFERRED_ACCOUNTS_TWITCH_LIST,
@@ -1494,6 +1496,7 @@ enum msg_hash_enums
MENU_LABEL(CORE_OPTIONS),
MENU_LABEL(NO_SHADER_PARAMETERS),
MENU_LABEL(NO_CORE_INFORMATION_AVAILABLE),
+ MENU_LABEL(NO_CORE_BACKUPS_AVAILABLE),
MENU_LABEL(NO_CORES_AVAILABLE),
/* Audio */
@@ -1936,6 +1939,30 @@ enum msg_hash_enums
MSG_ALL_CORES_UPDATED,
MSG_NUM_CORES_UPDATED,
+ /* Core backup/restore */
+ MENU_LABEL(CORE_CREATE_BACKUP),
+ MENU_LABEL(CORE_RESTORE_BACKUP_LIST),
+ MENU_LABEL(CORE_DELETE_BACKUP_LIST),
+
+ MENU_ENUM_LABEL_CORE_RESTORE_BACKUP_ENTRY,
+ MENU_ENUM_LABEL_CORE_DELETE_BACKUP_ENTRY,
+ MENU_ENUM_LABEL_VALUE_CORE_BACKUP_CRC,
+
+ MSG_CORE_BACKUP_SCANNING_CORE,
+ MSG_CORE_BACKUP_ALREADY_EXISTS,
+ MSG_BACKING_UP_CORE,
+ MSG_CORE_BACKUP_COMPLETE,
+ MSG_CORE_RESTORATION_ALREADY_INSTALLED,
+ MSG_RESTORING_CORE,
+ MSG_CORE_RESTORATION_COMPLETE,
+ MSG_CORE_INSTALLATION_ALREADY_INSTALLED,
+ MSG_INSTALLING_CORE,
+ MSG_CORE_INSTALLATION_COMPLETE,
+ MSG_CORE_RESTORATION_INVALID_CONTENT,
+ MSG_CORE_BACKUP_FAILED,
+ MSG_CORE_RESTORATION_FAILED,
+ MSG_CORE_INSTALLATION_FAILED,
+
MENU_LABEL(VIDEO_SHADER_PARAMETERS),
MENU_LABEL(VIDEO_SHADER_PRESET_PARAMETERS),
MENU_LABEL(DISK_OPTIONS),
diff --git a/retroarch.c b/retroarch.c
index befb7e43eb..dc90dd1f5e 100644
--- a/retroarch.c
+++ b/retroarch.c
@@ -70,6 +70,7 @@
#include
#include
#include
+#include
#include
#include
@@ -9150,6 +9151,8 @@ void main_exit(void *args)
ui_companion_driver_free();
frontend_driver_free();
+ rtime_deinit();
+
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
CoUninitialize();
#endif
@@ -9177,6 +9180,8 @@ int rarch_main(int argc, char *argv[], void *data)
}
#endif
+ rtime_init();
+
libretro_free_system_info(&p_rarch->runloop_system.info);
command_event(CMD_EVENT_HISTORY_DEINIT, NULL);
rarch_favorites_deinit();
diff --git a/runtime_file.c b/runtime_file.c
index 4c94b9d1ce..51a30d0fbc 100644
--- a/runtime_file.c
+++ b/runtime_file.c
@@ -32,6 +32,7 @@
#include
#include
#include
+#include
#include "file_path_special.h"
#include "paths.h"
@@ -534,30 +535,22 @@ void runtime_log_set_last_played(runtime_log_t *runtime_log,
void runtime_log_set_last_played_now(runtime_log_t *runtime_log)
{
time_t current_time;
- struct tm *time_info;
+ struct tm time_info;
if (!runtime_log)
return;
/* Get current time */
time(¤t_time);
- time_info = localtime(¤t_time);
-
- /* This can actually happen, but if does we probably
- * have bigger problems to worry about... */
- if(!time_info)
- {
- RARCH_ERR("Failed to get current time.\n");
- return;
- }
+ rtime_localtime(¤t_time, &time_info);
/* Extract values */
- runtime_log->last_played.year = (unsigned)time_info->tm_year + 1900;
- runtime_log->last_played.month = (unsigned)time_info->tm_mon + 1;
- runtime_log->last_played.day = (unsigned)time_info->tm_mday;
- runtime_log->last_played.hour = (unsigned)time_info->tm_hour;
- runtime_log->last_played.minute = (unsigned)time_info->tm_min;
- runtime_log->last_played.second = (unsigned)time_info->tm_sec;
+ runtime_log->last_played.year = (unsigned)time_info.tm_year + 1900;
+ runtime_log->last_played.month = (unsigned)time_info.tm_mon + 1;
+ runtime_log->last_played.day = (unsigned)time_info.tm_mday;
+ runtime_log->last_played.hour = (unsigned)time_info.tm_hour;
+ runtime_log->last_played.minute = (unsigned)time_info.tm_min;
+ runtime_log->last_played.second = (unsigned)time_info.tm_sec;
}
/* Resets log to default (zero) values */
diff --git a/tasks/task_core_backup.c b/tasks/task_core_backup.c
new file mode 100644
index 0000000000..6cf1921120
--- /dev/null
+++ b/tasks/task_core_backup.c
@@ -0,0 +1,961 @@
+/* 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
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include "../retroarch.h"
+#include "../paths.h"
+#include "../command.h"
+#include "../msg_hash.h"
+#include "../verbosity.h"
+#include "../core_info.h"
+#include "../core_backup.h"
+
+#define CORE_BACKUP_CHUNK_SIZE 4096
+
+enum core_backup_status
+{
+ CORE_BACKUP_BEGIN = 0,
+ CORE_BACKUP_CHECK_CRC,
+ CORE_BACKUP_PRE_ITERATE,
+ CORE_BACKUP_ITERATE,
+ CORE_BACKUP_END,
+ CORE_RESTORE_GET_CORE_CRC,
+ CORE_RESTORE_GET_BACKUP_CRC,
+ CORE_RESTORE_CHECK_CRC,
+ CORE_RESTORE_PRE_ITERATE,
+ CORE_RESTORE_ITERATE,
+ CORE_RESTORE_END
+};
+
+typedef struct core_backup_handle
+{
+ char *dir_core_assets;
+ char *core_path;
+ char *core_name;
+ char *backup_path;
+ enum core_backup_type backup_type;
+ enum core_backup_mode backup_mode;
+ int64_t core_file_size;
+ int64_t backup_file_size;
+ int64_t file_data_read;
+ uint32_t core_crc;
+ uint32_t backup_crc;
+ bool crc_match;
+ bool success;
+ intfstream_t *core_file;
+ intfstream_t *backup_file;
+ core_backup_list_t *backup_list;
+ enum core_backup_status status;
+} core_backup_handle_t;
+
+/*********************/
+/* Utility functions */
+/*********************/
+
+static void free_core_backup_handle(core_backup_handle_t *backup_handle)
+{
+ if (!backup_handle)
+ return;
+
+ if (backup_handle->dir_core_assets)
+ {
+ free(backup_handle->dir_core_assets);
+ backup_handle->dir_core_assets = NULL;
+ }
+
+ if (backup_handle->core_path)
+ {
+ free(backup_handle->core_path);
+ backup_handle->core_path = NULL;
+ }
+
+ if (backup_handle->core_name)
+ {
+ free(backup_handle->core_name);
+ backup_handle->core_name = NULL;
+ }
+
+ if (backup_handle->backup_path)
+ {
+ free(backup_handle->backup_path);
+ backup_handle->backup_path = NULL;
+ }
+
+ if (backup_handle->core_file)
+ {
+ intfstream_close(backup_handle->core_file);
+ free(backup_handle->core_file);
+ backup_handle->core_file = NULL;
+ }
+
+ if (backup_handle->backup_file)
+ {
+ intfstream_close(backup_handle->backup_file);
+ free(backup_handle->backup_file);
+ backup_handle->backup_file = NULL;
+ }
+
+ if (backup_handle->backup_list)
+ {
+ core_backup_list_free(backup_handle->backup_list);
+ backup_handle->backup_list = NULL;
+ }
+
+ free(backup_handle);
+ backup_handle = NULL;
+}
+
+/* Forward declarations, required for task_core_backup_finder() */
+static void task_core_backup_handler(retro_task_t *task);
+static void task_core_restore_handler(retro_task_t *task);
+
+static bool task_core_backup_finder(retro_task_t *task, void *user_data)
+{
+ core_backup_handle_t *backup_handle = NULL;
+ const char *core_filename_a = NULL;
+ const char *core_filename_b = NULL;
+
+ if (!task || !user_data)
+ return false;
+
+ if ((task->handler != task_core_backup_handler) &&
+ (task->handler != task_core_restore_handler))
+ return false;
+
+ backup_handle = (core_backup_handle_t*)task->state;
+ if (!backup_handle)
+ return false;
+
+ if (string_is_empty(backup_handle->core_path))
+ return false;
+
+ core_filename_a = path_basename((const char*)user_data);
+ core_filename_b = path_basename(backup_handle->core_path);
+
+ if (string_is_empty(core_filename_a) ||
+ string_is_empty(core_filename_b))
+ return false;
+
+ return string_is_equal(core_filename_a, core_filename_b);
+}
+
+/***************/
+/* Core Backup */
+/***************/
+
+static void task_core_backup_handler(retro_task_t *task)
+{
+ core_backup_handle_t *backup_handle = NULL;
+
+ if (!task)
+ goto task_finished;
+
+ backup_handle = (core_backup_handle_t*)task->state;
+
+ if (!backup_handle)
+ goto task_finished;
+
+ if (task_get_cancelled(task))
+ goto task_finished;
+
+ switch (backup_handle->status)
+ {
+ case CORE_BACKUP_BEGIN:
+ {
+ /* Get current list of backups */
+ backup_handle->backup_list = core_backup_list_init(
+ backup_handle->core_path, backup_handle->dir_core_assets);
+
+ /* Open core file */
+ backup_handle->core_file = intfstream_open_file(
+ backup_handle->core_path,
+ RETRO_VFS_FILE_ACCESS_READ,
+ RETRO_VFS_FILE_ACCESS_HINT_NONE);
+
+ if (!backup_handle->core_file)
+ {
+ RARCH_ERR("[core backup] Failed to open core file: %s\n",
+ backup_handle->core_path);
+ backup_handle->status = CORE_BACKUP_END;
+ break;
+ }
+
+ /* Get core file size */
+ backup_handle->core_file_size = intfstream_get_size(backup_handle->core_file);
+
+ if (backup_handle->core_file_size <= 0)
+ {
+ RARCH_ERR("[core backup] Core file is empty/invalid: %s\n",
+ backup_handle->core_path);
+ backup_handle->status = CORE_BACKUP_END;
+ break;
+ }
+
+ /* Go to crc checking phase */
+ backup_handle->status = CORE_BACKUP_CHECK_CRC;
+ }
+ break;
+ case CORE_BACKUP_CHECK_CRC:
+ {
+ /* Check whether we need to calculate crc value */
+ if (backup_handle->core_crc == 0)
+ {
+ if (!intfstream_get_crc(backup_handle->core_file,
+ &backup_handle->core_crc))
+ {
+ RARCH_ERR("[core backup] Failed to determine CRC of core file: %s\n",
+ backup_handle->core_path);
+ backup_handle->status = CORE_BACKUP_END;
+ break;
+ }
+ }
+
+ /* Check whether a backup with this crc already
+ * exists */
+ if (backup_handle->backup_list)
+ {
+ const core_backup_list_entry_t *entry = NULL;
+
+ if (core_backup_list_get_crc(
+ backup_handle->backup_list,
+ backup_handle->core_crc,
+ backup_handle->backup_mode,
+ &entry))
+ {
+ RARCH_LOG("[core backup] Current version of core is already backed up: %s\n",
+ entry->backup_path);
+
+ backup_handle->crc_match = true;
+ backup_handle->success = true;
+ backup_handle->status = CORE_BACKUP_END;
+ break;
+ }
+ }
+
+ /* Go to pre-iteration phase */
+ backup_handle->status = CORE_BACKUP_PRE_ITERATE;
+ }
+ break;
+ case CORE_BACKUP_PRE_ITERATE:
+ {
+ char task_title[PATH_MAX_LENGTH];
+ char backup_path[PATH_MAX_LENGTH];
+
+ task_title[0] = '\0';
+ backup_path[0] = '\0';
+
+ /* Get backup path */
+ if (!core_backup_get_backup_path(
+ backup_handle->core_path,
+ backup_handle->core_crc,
+ backup_handle->backup_mode,
+ backup_handle->dir_core_assets,
+ backup_path, sizeof(backup_path)))
+ {
+ RARCH_ERR("[core backup] Failed to generate backup path for core file: %s\n",
+ backup_handle->core_path);
+ backup_handle->status = CORE_BACKUP_END;
+ break;
+ }
+
+ backup_handle->backup_path = strdup(backup_path);
+
+ /* Open backup file */
+#if defined(HAVE_ZLIB)
+ backup_handle->backup_file = intfstream_open_rzip_file(
+ backup_handle->backup_path, RETRO_VFS_FILE_ACCESS_WRITE);
+#else
+ backup_handle->backup_file = intfstream_open_file(
+ backup_handle->backup_path, RETRO_VFS_FILE_ACCESS_WRITE,
+ RETRO_VFS_FILE_ACCESS_HINT_NONE);
+#endif
+ if (!backup_handle->backup_file)
+ {
+ RARCH_ERR("[core backup] Failed to open core backup file: %s\n",
+ backup_handle->backup_path);
+ backup_handle->status = CORE_BACKUP_END;
+ break;
+ }
+
+ /* Update task title */
+ task_free_title(task);
+ strlcpy(task_title, msg_hash_to_str(MSG_BACKING_UP_CORE),
+ sizeof(task_title));
+ strlcat(task_title, backup_handle->core_name, sizeof(task_title));
+ task_set_title(task, strdup(task_title));
+
+ /* Go to iteration phase */
+ backup_handle->status = CORE_BACKUP_ITERATE;
+ }
+ break;
+ case CORE_BACKUP_ITERATE:
+ {
+ int64_t data_read = 0;
+ int64_t data_written = 0;
+ uint8_t buffer[CORE_BACKUP_CHUNK_SIZE];
+
+ /* Read a single chunk from the core file */
+ data_read = intfstream_read(backup_handle->core_file, buffer, sizeof(buffer));
+
+ if (data_read < 0)
+ {
+ RARCH_ERR("[core backup] Failed to read from core file: %s\n",
+ backup_handle->core_path);
+ backup_handle->status = CORE_BACKUP_END;
+ break;
+ }
+
+ backup_handle->file_data_read += data_read;
+
+ /* Check whether we have reached the end of the file */
+ if (data_read == 0)
+ {
+ /* Close core file */
+ intfstream_close(backup_handle->core_file);
+ free(backup_handle->core_file);
+ backup_handle->core_file = NULL;
+
+ /* Close backup file */
+ intfstream_flush(backup_handle->backup_file);
+ intfstream_close(backup_handle->backup_file);
+ free(backup_handle->backup_file);
+ backup_handle->backup_file = NULL;
+
+ backup_handle->success = true;
+ backup_handle->status = CORE_BACKUP_END;
+ break;
+ }
+
+ /* Write chunk to backup file */
+ data_written = intfstream_write(backup_handle->backup_file, buffer, data_read);
+
+ if (data_written != data_read)
+ {
+ RARCH_ERR("[core backup] Failed to write to core backup file: %s\n",
+ backup_handle->backup_path);
+ backup_handle->status = CORE_BACKUP_END;
+ break;
+ }
+
+ /* Update progress display */
+ task_set_progress(task,
+ (backup_handle->file_data_read * 100) / backup_handle->core_file_size);
+ }
+ break;
+ case CORE_BACKUP_END:
+ {
+ char task_title[PATH_MAX_LENGTH];
+
+ task_title[0] = '\0';
+
+ /* Set final task title */
+ task_free_title(task);
+
+ if (backup_handle->success)
+ {
+ if (backup_handle->crc_match)
+ strlcpy(task_title, msg_hash_to_str(MSG_CORE_BACKUP_ALREADY_EXISTS),
+ sizeof(task_title));
+ else
+ strlcpy(task_title, msg_hash_to_str(MSG_CORE_BACKUP_COMPLETE),
+ sizeof(task_title));
+ }
+ else
+ strlcpy(task_title, msg_hash_to_str(MSG_CORE_BACKUP_FAILED),
+ sizeof(task_title));
+
+ strlcat(task_title, backup_handle->core_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_core_backup_handle(backup_handle);
+}
+
+/* Note: If crc is set to 0, crc of core_path file will
+ * be calculated automatically */
+void *task_push_core_backup(const char *core_path,
+ uint32_t crc, enum core_backup_mode backup_mode,
+ const char *dir_core_assets, bool mute)
+{
+ task_finder_data_t find_data;
+ core_info_ctx_find_t core_info;
+ const char *core_name = NULL;
+ retro_task_t *task = NULL;
+ core_backup_handle_t *backup_handle = NULL;
+ char task_title[PATH_MAX_LENGTH];
+
+ task_title[0] = '\0';
+
+ /* Sanity check */
+ if (string_is_empty(core_path) ||
+ !path_is_valid(core_path))
+ goto error;
+
+ /* Concurrent backup/restore tasks for the same core
+ * are not allowed */
+ find_data.func = task_core_backup_finder;
+ find_data.userdata = (void*)core_path;
+
+ if (task_queue_find(&find_data))
+ goto error;
+
+ /* Get core name */
+ core_info.inf = NULL;
+ core_info.path = core_path;
+
+ /* If core is found, use display name */
+ if (core_info_find(&core_info, core_path) &&
+ core_info.inf->display_name)
+ core_name = core_info.inf->display_name;
+ else
+ {
+ /* If not, use core file name */
+ core_name = path_basename(core_path);
+
+ if (string_is_empty(core_name))
+ goto error;
+ }
+
+ /* Configure handle */
+ backup_handle = (core_backup_handle_t*)calloc(1, sizeof(core_backup_handle_t));
+
+ if (!backup_handle)
+ goto error;
+
+ backup_handle->dir_core_assets = string_is_empty(dir_core_assets) ? NULL : strdup(dir_core_assets);
+ backup_handle->core_path = strdup(core_path);
+ backup_handle->core_name = strdup(core_name);
+ backup_handle->backup_path = NULL;
+ backup_handle->backup_type = CORE_BACKUP_TYPE_ARCHIVE;
+ backup_handle->backup_mode = backup_mode;
+ backup_handle->core_file_size = 0;
+ backup_handle->backup_file_size = 0;
+ backup_handle->file_data_read = 0;
+ backup_handle->core_crc = crc;
+ backup_handle->backup_crc = 0;
+ backup_handle->crc_match = false;
+ backup_handle->success = false;
+ backup_handle->core_file = NULL;
+ backup_handle->backup_file = NULL;
+ backup_handle->backup_list = NULL;
+ backup_handle->status = CORE_BACKUP_BEGIN;
+
+ /* Create task */
+ task = task_init();
+
+ if (!task)
+ goto error;
+
+ /* Get initial task title */
+ strlcpy(task_title, msg_hash_to_str(MSG_CORE_BACKUP_SCANNING_CORE),
+ sizeof(task_title));
+ strlcat(task_title, backup_handle->core_name, sizeof(task_title));
+
+ /* Configure task */
+ task->handler = task_core_backup_handler;
+ task->state = backup_handle;
+ task->mute = mute;
+ task->title = strdup(task_title);
+ 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 */
+ free_core_backup_handle(backup_handle);
+
+ return NULL;
+}
+
+/****************/
+/* Core Restore */
+/****************/
+
+/* Unloads core if it is currently loaded
+ * > Returns true if core was unloaded */
+static bool task_core_restore_unload_core(const char *core_path)
+{
+ const char *core_filename = NULL;
+ const char *loaded_core_path = NULL;
+ const char *loaded_core_filename = NULL;
+
+ if (string_is_empty(core_path))
+ return false;
+
+ /* Get core file name */
+ core_filename = path_basename(core_path);
+ if (string_is_empty(core_filename))
+ return false;
+
+ /* Get loaded core file name */
+ loaded_core_path = path_get(RARCH_PATH_CORE);
+ if (string_is_empty(loaded_core_path))
+ return false;
+
+ loaded_core_filename = path_basename(loaded_core_path);
+ if (string_is_empty(loaded_core_filename))
+ return false;
+
+ /* Check if whether file names match */
+ if (string_is_equal(core_filename, loaded_core_filename))
+ {
+ command_event(CMD_EVENT_UNLOAD_CORE, NULL);
+ return true;
+ }
+
+ return false;
+}
+
+static void cb_task_core_restore(
+ 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);
+}
+
+static void task_core_restore_handler(retro_task_t *task)
+{
+ core_backup_handle_t *backup_handle = NULL;
+
+ if (!task)
+ goto task_finished;
+
+ backup_handle = (core_backup_handle_t*)task->state;
+
+ if (!backup_handle)
+ goto task_finished;
+
+ if (task_get_cancelled(task))
+ goto task_finished;
+
+ switch (backup_handle->status)
+ {
+ case CORE_RESTORE_GET_CORE_CRC:
+ {
+ /* If core file already exists, get its current
+ * crc value */
+ if (path_is_valid(backup_handle->core_path))
+ {
+ /* Open core file for reading */
+ backup_handle->core_file = intfstream_open_file(
+ backup_handle->core_path, RETRO_VFS_FILE_ACCESS_READ,
+ RETRO_VFS_FILE_ACCESS_HINT_NONE);
+
+ if (!backup_handle->core_file)
+ {
+ RARCH_ERR("[core restore] Failed to open core file: %s\n",
+ backup_handle->core_path);
+ backup_handle->status = CORE_RESTORE_END;
+ break;
+ }
+
+ /* Get crc value */
+ if (!intfstream_get_crc(backup_handle->core_file,
+ &backup_handle->core_crc))
+ {
+ RARCH_ERR("[core restore] Failed to determine CRC of core file: %s\n",
+ backup_handle->core_path);
+ backup_handle->status = CORE_RESTORE_END;
+ break;
+ }
+
+ /* Close core file */
+ intfstream_close(backup_handle->core_file);
+ free(backup_handle->core_file);
+ backup_handle->core_file = NULL;
+ }
+
+ /* Go to next crc gathering phase */
+ backup_handle->status = CORE_RESTORE_GET_BACKUP_CRC;
+ }
+ break;
+ case CORE_RESTORE_GET_BACKUP_CRC:
+ {
+ /* Get crc value of backup file */
+ if (!core_backup_get_backup_crc(
+ backup_handle->backup_path, &backup_handle->backup_crc))
+ {
+ RARCH_ERR("[core restore] Failed to determine CRC of core backup file: %s\n",
+ backup_handle->backup_path);
+ backup_handle->status = CORE_RESTORE_END;
+ break;
+ }
+
+ /* Go to crc comparison phase */
+ backup_handle->status = CORE_RESTORE_CHECK_CRC;
+ }
+ break;
+ case CORE_RESTORE_CHECK_CRC:
+ {
+ /* Check whether current core matches backup crc */
+ if (backup_handle->core_crc == backup_handle->backup_crc)
+ {
+ RARCH_LOG("[core restore] Selected backup core file is already installed: %s\n",
+ backup_handle->backup_path);
+
+ backup_handle->crc_match = true;
+ backup_handle->success = true;
+ backup_handle->status = CORE_RESTORE_END;
+ break;
+ }
+
+ /* Go to pre-iteration phase */
+ backup_handle->status = CORE_RESTORE_PRE_ITERATE;
+ }
+ break;
+ case CORE_RESTORE_PRE_ITERATE:
+ {
+ char task_title[PATH_MAX_LENGTH];
+
+ task_title[0] = '\0';
+
+ /* Open backup file */
+#if defined(HAVE_ZLIB)
+ backup_handle->backup_file = intfstream_open_rzip_file(
+ backup_handle->backup_path, RETRO_VFS_FILE_ACCESS_READ);
+#else
+ backup_handle->backup_file = intfstream_open_file(
+ backup_handle->backup_path, RETRO_VFS_FILE_ACCESS_READ,
+ RETRO_VFS_FILE_ACCESS_HINT_NONE);
+#endif
+ if (!backup_handle->backup_file)
+ {
+ RARCH_ERR("[core restore] Failed to open core backup file: %s\n",
+ backup_handle->backup_path);
+ backup_handle->status = CORE_RESTORE_END;
+ break;
+ }
+
+ /* Get backup file size */
+ backup_handle->backup_file_size = intfstream_get_size(backup_handle->backup_file);
+
+ if (backup_handle->backup_file_size <= 0)
+ {
+ RARCH_ERR("[core restore] Core backup file is empty/invalid: %s\n",
+ backup_handle->backup_path);
+ backup_handle->status = CORE_RESTORE_END;
+ break;
+ }
+
+ /* Open core file for writing */
+ backup_handle->core_file = intfstream_open_file(
+ backup_handle->core_path, RETRO_VFS_FILE_ACCESS_WRITE,
+ RETRO_VFS_FILE_ACCESS_HINT_NONE);
+
+ if (!backup_handle->core_file)
+ {
+ RARCH_ERR("[core restore] Failed to open core file: %s\n",
+ backup_handle->core_path);
+ backup_handle->status = CORE_RESTORE_END;
+ break;
+ }
+
+ /* Update task title */
+ task_free_title(task);
+ strlcpy(task_title, (backup_handle->backup_type == CORE_BACKUP_TYPE_ARCHIVE) ?
+ msg_hash_to_str(MSG_RESTORING_CORE) :
+ msg_hash_to_str(MSG_INSTALLING_CORE),
+ sizeof(task_title));
+ strlcat(task_title, backup_handle->core_name, sizeof(task_title));
+ task_set_title(task, strdup(task_title));
+
+ /* Go to iteration phase */
+ backup_handle->status = CORE_RESTORE_ITERATE;
+ }
+ break;
+ case CORE_RESTORE_ITERATE:
+ {
+ int64_t data_read = 0;
+ int64_t data_written = 0;
+ uint8_t buffer[CORE_BACKUP_CHUNK_SIZE];
+
+ /* Read a single chunk from the backup file */
+ data_read = intfstream_read(backup_handle->backup_file, buffer, sizeof(buffer));
+
+ if (data_read < 0)
+ {
+ RARCH_ERR("[core restore] Failed to read from core backup file: %s\n",
+ backup_handle->backup_path);
+ backup_handle->status = CORE_RESTORE_END;
+ break;
+ }
+
+ backup_handle->file_data_read += data_read;
+
+ /* Check whether we have reached the end of the file */
+ if (data_read == 0)
+ {
+ /* Close backup file */
+ intfstream_close(backup_handle->backup_file);
+ free(backup_handle->backup_file);
+ backup_handle->backup_file = NULL;
+
+ /* Close core file */
+ intfstream_flush(backup_handle->core_file);
+ intfstream_close(backup_handle->core_file);
+ free(backup_handle->core_file);
+ backup_handle->core_file = NULL;
+
+ backup_handle->success = true;
+ backup_handle->status = CORE_RESTORE_END;
+ break;
+ }
+
+ /* Write chunk to core file */
+ data_written = intfstream_write(backup_handle->core_file, buffer, data_read);
+
+ if (data_written != data_read)
+ {
+ RARCH_ERR("[core restore] Failed to write to core file: %s\n",
+ backup_handle->core_path);
+ backup_handle->status = CORE_RESTORE_END;
+ break;
+ }
+
+ /* Update progress display */
+ task_set_progress(task,
+ (backup_handle->file_data_read * 100) / backup_handle->backup_file_size);
+ }
+ break;
+ case CORE_RESTORE_END:
+ {
+ char task_title[PATH_MAX_LENGTH];
+
+ task_title[0] = '\0';
+
+ /* Set final task title */
+ task_free_title(task);
+
+ if (backup_handle->success)
+ {
+ if (backup_handle->crc_match)
+ strlcpy(task_title, (backup_handle->backup_type == CORE_BACKUP_TYPE_ARCHIVE) ?
+ msg_hash_to_str(MSG_CORE_RESTORATION_ALREADY_INSTALLED) :
+ msg_hash_to_str(MSG_CORE_INSTALLATION_ALREADY_INSTALLED),
+ sizeof(task_title));
+ else
+ strlcpy(task_title, (backup_handle->backup_type == CORE_BACKUP_TYPE_ARCHIVE) ?
+ msg_hash_to_str(MSG_CORE_RESTORATION_COMPLETE) :
+ msg_hash_to_str(MSG_CORE_INSTALLATION_COMPLETE),
+ sizeof(task_title));
+ }
+ else
+ strlcpy(task_title, (backup_handle->backup_type == CORE_BACKUP_TYPE_ARCHIVE) ?
+ msg_hash_to_str(MSG_CORE_RESTORATION_FAILED) :
+ msg_hash_to_str(MSG_CORE_INSTALLATION_FAILED),
+ sizeof(task_title));
+
+ strlcat(task_title, backup_handle->core_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_core_backup_handle(backup_handle);
+}
+
+bool task_push_core_restore(const char *backup_path, const char *dir_libretro,
+ bool *core_loaded)
+{
+ task_finder_data_t find_data;
+ core_info_ctx_find_t core_info;
+ enum core_backup_type backup_type;
+ const char *core_name = NULL;
+ retro_task_t *task = NULL;
+ core_backup_handle_t *backup_handle = NULL;
+ char core_path[PATH_MAX_LENGTH];
+ char task_title[PATH_MAX_LENGTH];
+
+ core_path[0] = '\0';
+ task_title[0] = '\0';
+
+ /* Sanity check */
+ if (string_is_empty(backup_path) ||
+ !path_is_valid(backup_path) ||
+ string_is_empty(dir_libretro) ||
+ !core_loaded)
+ goto error;
+
+ /* Ensure core directory is valid */
+ if (!path_is_directory(dir_libretro))
+ {
+ if (!path_mkdir(dir_libretro))
+ {
+ RARCH_ERR("[core restore] Failed to create core directory: %s\n", dir_libretro);
+ goto error;
+ }
+ }
+
+ /* Get core path */
+ backup_type = core_backup_get_core_path(
+ backup_path, dir_libretro, core_path, sizeof(core_path));
+
+ if (backup_type == CORE_BACKUP_TYPE_INVALID)
+ {
+ const char *backup_filename = path_basename(backup_path);
+ char msg[PATH_MAX_LENGTH];
+
+ msg[0] = '\0';
+
+ strlcpy(msg, msg_hash_to_str(MSG_CORE_RESTORATION_INVALID_CONTENT), sizeof(msg));
+ strlcat(msg, backup_filename ? backup_filename : "", sizeof(msg));
+
+ RARCH_ERR("[core restore] Invalid core file selected: %s\n", backup_path);
+ runloop_msg_queue_push(msg, 1, 100, true,
+ NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
+ goto error;
+ }
+
+ /* Concurrent backup/restore tasks for the same core
+ * are not allowed */
+ find_data.func = task_core_backup_finder;
+ find_data.userdata = (void*)core_path;
+
+ if (task_queue_find(&find_data))
+ goto error;
+
+ /* Get core name */
+ core_info.inf = NULL;
+ core_info.path = core_path;
+
+ /* If core is found, use display name */
+ if (core_info_find(&core_info, core_path) &&
+ core_info.inf->display_name)
+ core_name = core_info.inf->display_name;
+ else
+ {
+ /* If not, use core file name */
+ core_name = path_basename(core_path);
+
+ if (string_is_empty(core_name))
+ goto error;
+ }
+
+ /* Configure handle */
+ backup_handle = (core_backup_handle_t*)calloc(1, sizeof(core_backup_handle_t));
+
+ if (!backup_handle)
+ goto error;
+
+ backup_handle->dir_core_assets = NULL;
+ backup_handle->core_path = strdup(core_path);
+ backup_handle->core_name = strdup(core_name);
+ backup_handle->backup_path = strdup(backup_path);
+ backup_handle->backup_type = backup_type;
+ backup_handle->backup_mode = CORE_BACKUP_MODE_MANUAL;
+ backup_handle->core_file_size = 0;
+ backup_handle->backup_file_size = 0;
+ backup_handle->file_data_read = 0;
+ backup_handle->core_crc = 0;
+ backup_handle->backup_crc = 0;
+ backup_handle->crc_match = false;
+ backup_handle->success = false;
+ backup_handle->core_file = NULL;
+ backup_handle->backup_file = NULL;
+ backup_handle->backup_list = NULL;
+ backup_handle->status = CORE_RESTORE_GET_CORE_CRC;
+
+ /* Create task */
+ task = task_init();
+
+ if (!task)
+ goto error;
+
+ /* Get initial task title */
+ strlcpy(task_title, msg_hash_to_str(MSG_CORE_BACKUP_SCANNING_CORE),
+ sizeof(task_title));
+ strlcat(task_title, backup_handle->core_name, sizeof(task_title));
+
+ /* Configure task */
+ task->handler = task_core_restore_handler;
+ task->state = backup_handle;
+ task->title = strdup(task_title);
+ task->alternative_look = true;
+ task->progress = 0;
+ task->callback = cb_task_core_restore;
+
+ /* If core to be restored is currently loaded, must
+ * unload it before pushing the task */
+ *core_loaded = task_core_restore_unload_core(core_path);
+
+ /* Push task */
+ task_queue_push(task);
+
+ return true;
+
+error:
+
+ /* Clean up task */
+ if (task)
+ {
+ free(task);
+ task = NULL;
+ }
+
+ /* Clean up handle */
+ free_core_backup_handle(backup_handle);
+
+ return false;
+}
diff --git a/tasks/task_core_updater.c b/tasks/task_core_updater.c
index 6107eac3bd..009c442555 100644
--- a/tasks/task_core_updater.c
+++ b/tasks/task_core_updater.c
@@ -25,8 +25,8 @@
#include
#include
#include
+#include
#include
-#include
#include "task_file_transfer.h"
#include "tasks_internal.h"
@@ -130,21 +130,26 @@ static bool local_core_matches_remote_crc(
if (path_is_valid(local_core_path))
{
- int64_t length = 0;
- uint8_t *ret_buf = NULL;
+ /* Open core file */
+ intfstream_t *local_core_file = intfstream_open_file(
+ local_core_path, RETRO_VFS_FILE_ACCESS_READ,
+ RETRO_VFS_FILE_ACCESS_HINT_NONE);
- if (filestream_read_file(
- local_core_path, (void**)&ret_buf, &length))
+ if (local_core_file)
{
uint32_t crc = 0;
+ bool success = false;
- if (length >= 0)
- crc = encoding_crc32(0, ret_buf, length);
+ /* Get crc value */
+ success = intfstream_get_crc(local_core_file, &crc);
- if (ret_buf)
- free(ret_buf);
+ /* Close core file */
+ intfstream_close(local_core_file);
+ free(local_core_file);
+ local_core_file = NULL;
- if ((crc != 0) && (crc == remote_crc))
+ /* Check whether crc matches remote file */
+ if (success && (crc != 0) && (crc == remote_crc))
return true;
}
}
diff --git a/tasks/task_database.c b/tasks/task_database.c
index 6747d79d08..c5207fd8c0 100644
--- a/tasks/task_database.c
+++ b/tasks/task_database.c
@@ -343,27 +343,10 @@ static int task_database_chd_get_serial(const char *name, char* serial)
return result;
}
-static int intfstream_get_crc(intfstream_t *fd, uint32_t *crc)
-{
- int64_t read = 0;
- uint32_t acc = 0;
- uint8_t buffer[4096];
-
- while ((read = intfstream_read(fd, buffer, sizeof(buffer))) > 0)
- acc = encoding_crc32(acc, buffer, (size_t)read);
-
- if (read < 0)
- return 0;
-
- *crc = acc;
-
- return 1;
-}
-
static bool intfstream_file_get_crc(const char *name,
uint64_t offset, size_t size, uint32_t *crc)
{
- int rv;
+ bool rv;
intfstream_t *fd = intfstream_open_file(name,
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
uint8_t *data = NULL;
@@ -485,7 +468,7 @@ static int task_database_gdi_get_crc(const char *name, uint32_t *crc)
static bool task_database_chd_get_crc(const char *name, uint32_t *crc)
{
- int rv;
+ bool rv;
intfstream_t *fd = intfstream_open_chd_track(
name,
RETRO_VFS_FILE_ACCESS_READ,
@@ -495,7 +478,7 @@ static bool task_database_chd_get_crc(const char *name, uint32_t *crc)
return 0;
rv = intfstream_get_crc(fd, crc);
- if (rv == 1)
+ if (rv)
{
RARCH_LOG("CHD '%s' crc: %x\n", name, *crc);
}
diff --git a/tasks/task_save.c b/tasks/task_save.c
index 105d45b0b2..07e6a7efaf 100644
--- a/tasks/task_save.c
+++ b/tasks/task_save.c
@@ -36,6 +36,7 @@
#include
#include
#include
+#include
#ifdef HAVE_CONFIG_H
#include "../core.h"
@@ -1570,6 +1571,7 @@ static bool dump_to_file_desperate(const void *data,
size_t size, unsigned type)
{
time_t time_;
+ struct tm tm_;
char *timebuf;
char *path;
char *application_data = (char*)malloc(PATH_MAX_LENGTH * sizeof(char));
@@ -1587,9 +1589,11 @@ static bool dump_to_file_desperate(const void *data,
timebuf = (char*)malloc(256 * sizeof(char));
timebuf[0] = '\0';
+ rtime_localtime(&time_, &tm_);
+
strftime(timebuf,
256 * sizeof(char),
- "%Y-%m-%d-%H-%M-%S", localtime(&time_));
+ "%Y-%m-%d-%H-%M-%S", &tm_);
path = (char*)malloc(PATH_MAX_LENGTH * sizeof(char));
path[0] = '\0';
diff --git a/tasks/tasks_internal.h b/tasks/tasks_internal.h
index eb8554c0cb..9acabac54e 100644
--- a/tasks/tasks_internal.h
+++ b/tasks/tasks_internal.h
@@ -37,6 +37,9 @@
#include "../playlist.h"
#endif
+/* Required for task_push_core_backup() */
+#include "../core_backup.h"
+
RETRO_BEGIN_DECLS
typedef struct nbio_buf
@@ -100,6 +103,18 @@ void *task_push_core_updater_download(
bool mute, bool check_crc, const char *path_dir_libretro);
void task_push_update_installed_cores(const char *path_dir_libretro);
+/* Core backup/restore tasks */
+
+/* Note: If crc is set to 0, crc of core_path file will
+ * be calculated automatically */
+void *task_push_core_backup(const char *core_path,
+ uint32_t crc, enum core_backup_mode backup_mode,
+ const char *dir_core_assets, bool mute);
+/* Note: If 'core_loaded' is true, menu stack should be
+ * flushed if task_push_core_restore() returns true */
+bool task_push_core_restore(const char *backup_path, const char *dir_libretro,
+ bool *core_loaded);
+
bool task_push_pl_entry_thumbnail_download(
const char *system,
playlist_t *playlist,
diff --git a/verbosity.c b/verbosity.c
index 4dddeebe2f..5acdadf29c 100644
--- a/verbosity.c
+++ b/verbosity.c
@@ -48,6 +48,7 @@
#include
#include
#include
+#include
#include
#ifdef HAVE_CONFIG_H
@@ -428,11 +429,13 @@ void rarch_log_file_init(
if (string_is_empty(timestamped_log_file_name))
{
char format[256];
- time_t cur_time = time(NULL);
- const struct tm *tm_ = localtime(&cur_time);
+ struct tm tm_;
+ time_t cur_time = time(NULL);
+
+ rtime_localtime(&cur_time, &tm_);
format[0] = '\0';
- strftime(format, sizeof(format), "retroarch__%Y_%m_%d__%H_%M_%S", tm_);
+ strftime(format, sizeof(format), "retroarch__%Y_%m_%d__%H_%M_%S", &tm_);
fill_pathname_noext(timestamped_log_file_name, format,
".log",
sizeof(timestamped_log_file_name));