1
0
mirror of https://github.com/libretro/RetroArch synced 2024-07-03 00:38:44 +00:00

Add option to backup/restore installed cores

This commit is contained in:
jdgleaver 2020-06-04 12:19:24 +01:00
parent a3cce404b6
commit 0a33e562f4
35 changed files with 2675 additions and 256 deletions

View File

@ -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

744
core_backup.c Normal file
View File

@ -0,0 +1,744 @@
/* Copyright (C) 2010-2019 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (core_backup.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.
*/
#include <string/stdstring.h>
#include <lists/string_list.h>
#include <file/file_path.h>
#include <streams/interface_stream.h>
#include <streams/file_stream.h>
#include <lists/dir_list.h>
#include <time/rtime.h>
#include <retro_miscellaneous.h>
#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(&current_time);
rtime_localtime(&current_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
* - <core_filename>.<timestamp>.<crc>.<backup_mode>
* - 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;
}

175
core_backup.h Normal file
View File

@ -0,0 +1,175 @@
/* Copyright (C) 2010-2019 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (core_backup.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 __CORE_BACKUP_H
#define __CORE_BACKUP_H
#include <retro_common_api.h>
#include <libretro.h>
#include <boolean.h>
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

View File

@ -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(

View File

@ -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

View File

@ -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;

View File

@ -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"

View File

@ -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"

View File

@ -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 */

View File

@ -32,6 +32,7 @@
#include <file/file_path.h>
#include <retro_assert.h>
#include <string/stdstring.h>
#include <time/rtime.h>
/* 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,

View File

@ -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);

View File

@ -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 <retro_common_api.h>
#include <stdint.h>
#include <stddef.h>
#include <time.h>
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

View File

@ -31,6 +31,7 @@
#if defined(HAVE_ZLIB)
#include <streams/rzip_stream.h>
#endif
#include <encodings/crc32.h>
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)
{

View File

@ -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 <rthreads/rthreads.h>
#include <retro_assert.h>
#include <stdlib.h>
#endif
#include <string.h>
#include <time/rtime.h>
#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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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];

View File

@ -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];

View File

@ -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 */

View File

@ -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:

View File

@ -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
};

View File

@ -28,6 +28,7 @@
#include <streams/file_stream.h>
#include <string/stdstring.h>
#include <encodings/utf.h>
#include <time/rtime.h>
#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

View File

@ -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
};

View File

@ -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),

View File

@ -70,6 +70,7 @@
#include <retro_math.h>
#include <retro_timers.h>
#include <encodings/utf.h>
#include <time/rtime.h>
#include <gfx/scaler/pixconv.h>
#include <gfx/scaler/scaler.h>
@ -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();

View File

@ -32,6 +32,7 @@
#include <formats/jsonsax_full.h>
#include <string/stdstring.h>
#include <encodings/utf.h>
#include <time/rtime.h>
#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(&current_time);
time_info = localtime(&current_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(&current_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 */

961
tasks/task_core_backup.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <boolean.h>
#include <string/stdstring.h>
#include <file/file_path.h>
#include <streams/interface_stream.h>
#include <streams/file_stream.h>
#include <streams/rzip_stream.h>
#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;
}

View File

@ -25,8 +25,8 @@
#include <string/stdstring.h>
#include <file/file_path.h>
#include <net/net_http.h>
#include <streams/interface_stream.h>
#include <streams/file_stream.h>
#include <encodings/crc32.h>
#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;
}
}

View File

@ -348,27 +348,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;
@ -490,7 +473,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,
@ -500,7 +483,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);
}

View File

@ -36,6 +36,7 @@
#include <file/file_path.h>
#include <retro_miscellaneous.h>
#include <string/stdstring.h>
#include <time/rtime.h>
#ifdef HAVE_CONFIG_H
#include "../core.h"
@ -1567,6 +1568,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));
@ -1584,9 +1586,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';

View File

@ -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,

View File

@ -48,6 +48,7 @@
#include <string/stdstring.h>
#include <streams/file_stream.h>
#include <compat/fopen_utf8.h>
#include <time/rtime.h>
#include <retro_miscellaneous.h>
#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));