mirror of
https://github.com/libretro/RetroArch
synced 2025-01-29 18:32:44 +00:00
Add option to backup/restore installed cores
This commit is contained in:
parent
a3cce404b6
commit
0a33e562f4
@ -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
744
core_backup.c
Normal 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(¤t_time);
|
||||
rtime_localtime(¤t_time, &time_info);
|
||||
|
||||
/* Generate backup filename */
|
||||
n = snprintf(backup_filename, sizeof(backup_filename),
|
||||
"%s.%04u%02u%02uT%02u%02u%02u.%08x.%u%s",
|
||||
core_filename,
|
||||
(unsigned)time_info.tm_year + 1900,
|
||||
(unsigned)time_info.tm_mon + 1,
|
||||
(unsigned)time_info.tm_mday,
|
||||
(unsigned)time_info.tm_hour,
|
||||
(unsigned)time_info.tm_min,
|
||||
(unsigned)time_info.tm_sec,
|
||||
crc,
|
||||
(unsigned)backup_mode,
|
||||
file_path_str(FILE_PATH_CORE_BACKUP_EXTENSION));
|
||||
|
||||
if ((n < 0) || (n >= 128))
|
||||
n = 0; /* Silence GCC warnings... */
|
||||
|
||||
/* Build final path */
|
||||
fill_pathname_join(backup_path, backup_dir,
|
||||
backup_filename, len);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Returns detected type of specified core backup file */
|
||||
enum core_backup_type core_backup_get_backup_type(const char *backup_path)
|
||||
{
|
||||
const char *backup_ext = NULL;
|
||||
struct string_list *metadata_list = NULL;
|
||||
char core_ext[255];
|
||||
|
||||
core_ext[0] = '\0';
|
||||
|
||||
if (string_is_empty(backup_path) || !path_is_valid(backup_path))
|
||||
goto error;
|
||||
|
||||
/* Get backup file extension */
|
||||
backup_ext = path_get_extension(backup_path);
|
||||
|
||||
if (string_is_empty(backup_ext))
|
||||
goto error;
|
||||
|
||||
/* Get platform-specific dynamic library extension */
|
||||
if (!frontend_driver_get_core_extension(core_ext, sizeof(core_ext)))
|
||||
goto error;
|
||||
|
||||
/* Check if this is an archived backup */
|
||||
if (string_is_equal_noncase(backup_ext,
|
||||
file_path_str(FILE_PATH_CORE_BACKUP_EXTENSION_NO_DOT)))
|
||||
{
|
||||
const char *backup_filename = NULL;
|
||||
const char *src_ext = NULL;
|
||||
|
||||
/* Split the backup filename into its various
|
||||
* metadata components */
|
||||
backup_filename = path_basename(backup_path);
|
||||
|
||||
if (string_is_empty(backup_filename))
|
||||
goto error;
|
||||
|
||||
metadata_list = string_split(backup_filename, ".");
|
||||
|
||||
if (!metadata_list || (metadata_list->size != 6))
|
||||
goto error;
|
||||
|
||||
/* Get extension of source core file */
|
||||
src_ext = metadata_list->elems[1].data;
|
||||
|
||||
if (string_is_empty(src_ext))
|
||||
goto error;
|
||||
|
||||
/* Check whether extension is valid */
|
||||
if (!string_is_equal_noncase(src_ext, core_ext))
|
||||
goto error;
|
||||
|
||||
string_list_free(metadata_list);
|
||||
metadata_list = NULL;
|
||||
|
||||
return CORE_BACKUP_TYPE_ARCHIVE;
|
||||
}
|
||||
|
||||
/* Check if this is a plain dynamic library file */
|
||||
if (string_is_equal_noncase(backup_ext, core_ext))
|
||||
return CORE_BACKUP_TYPE_LIB;
|
||||
|
||||
error:
|
||||
if (metadata_list)
|
||||
{
|
||||
string_list_free(metadata_list);
|
||||
metadata_list = NULL;
|
||||
}
|
||||
|
||||
return CORE_BACKUP_TYPE_INVALID;
|
||||
}
|
||||
|
||||
/* Fetches crc value of specified core backup file.
|
||||
* Returns true if successful */
|
||||
bool core_backup_get_backup_crc(char *backup_path, uint32_t *crc)
|
||||
{
|
||||
struct string_list *metadata_list = NULL;
|
||||
enum core_backup_type backup_type;
|
||||
|
||||
if (string_is_empty(backup_path) || !crc)
|
||||
goto error;
|
||||
|
||||
/* Get backup type */
|
||||
backup_type = core_backup_get_backup_type(backup_path);
|
||||
|
||||
switch (backup_type)
|
||||
{
|
||||
case CORE_BACKUP_TYPE_ARCHIVE:
|
||||
{
|
||||
const char *backup_filename = NULL;
|
||||
const char *crc_str = NULL;
|
||||
|
||||
/* Split the backup filename into its various
|
||||
* metadata components */
|
||||
backup_filename = path_basename(backup_path);
|
||||
|
||||
if (string_is_empty(backup_filename))
|
||||
goto error;
|
||||
|
||||
metadata_list = string_split(backup_filename, ".");
|
||||
|
||||
if (!metadata_list || (metadata_list->size != 6))
|
||||
goto error;
|
||||
|
||||
/* Get crc string */
|
||||
crc_str = metadata_list->elems[3].data;
|
||||
|
||||
if (string_is_empty(crc_str))
|
||||
goto error;
|
||||
|
||||
/* Convert to an integer */
|
||||
*crc = (uint32_t)string_hex_to_unsigned(crc_str);
|
||||
|
||||
if (*crc == 0)
|
||||
goto error;
|
||||
|
||||
string_list_free(metadata_list);
|
||||
metadata_list = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case CORE_BACKUP_TYPE_LIB:
|
||||
{
|
||||
intfstream_t *backup_file = NULL;
|
||||
|
||||
/* This is a plain dynamic library file,
|
||||
* have to read file data to determine crc */
|
||||
|
||||
/* Open backup file */
|
||||
backup_file = intfstream_open_file(
|
||||
backup_path, RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
|
||||
if (backup_file)
|
||||
{
|
||||
bool success;
|
||||
|
||||
/* Get crc value */
|
||||
success = intfstream_get_crc(backup_file, crc);
|
||||
|
||||
/* Close backup file */
|
||||
intfstream_close(backup_file);
|
||||
free(backup_file);
|
||||
backup_file = NULL;
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* Backup is invalid */
|
||||
break;
|
||||
}
|
||||
|
||||
error:
|
||||
if (metadata_list)
|
||||
{
|
||||
string_list_free(metadata_list);
|
||||
metadata_list = NULL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Fetches core path associated with specified core
|
||||
* backup file. Returns detected type of backup
|
||||
* file - CORE_BACKUP_TYPE_INVALID indicates that
|
||||
* backup file cannot be restored/installed, or
|
||||
* arguments are otherwise invalid */
|
||||
enum core_backup_type core_backup_get_core_path(
|
||||
const char *backup_path, const char *dir_libretro,
|
||||
char *core_path, size_t len)
|
||||
{
|
||||
const char *backup_filename = NULL;
|
||||
char *core_filename = NULL;
|
||||
enum core_backup_type backup_type = CORE_BACKUP_TYPE_INVALID;
|
||||
|
||||
if (string_is_empty(backup_path) || string_is_empty(dir_libretro))
|
||||
return backup_type;
|
||||
|
||||
backup_filename = path_basename(backup_path);
|
||||
|
||||
if (string_is_empty(backup_filename))
|
||||
return backup_type;
|
||||
|
||||
/* Check backup type */
|
||||
switch (core_backup_get_backup_type(backup_path))
|
||||
{
|
||||
case CORE_BACKUP_TYPE_ARCHIVE:
|
||||
{
|
||||
char *period = NULL;
|
||||
|
||||
/* This is an archived backup with timestamp/crc
|
||||
* metadata in the filename */
|
||||
core_filename = strdup(backup_filename);
|
||||
|
||||
/* Find the location of the second period */
|
||||
period = strchr(core_filename, '.');
|
||||
if (!period || (*(++period) == '\0'))
|
||||
break;
|
||||
|
||||
period = strchr(period, '.');
|
||||
if (!period)
|
||||
break;
|
||||
|
||||
/* Trim everything after (and including) the
|
||||
* second period */
|
||||
*period = '\0';
|
||||
|
||||
if (string_is_empty(core_filename))
|
||||
break;
|
||||
|
||||
/* All good - build core path */
|
||||
fill_pathname_join(core_path, dir_libretro,
|
||||
core_filename, len);
|
||||
|
||||
backup_type = CORE_BACKUP_TYPE_ARCHIVE;
|
||||
}
|
||||
break;
|
||||
case CORE_BACKUP_TYPE_LIB:
|
||||
/* This is a plain dynamic library file */
|
||||
fill_pathname_join(core_path, dir_libretro,
|
||||
backup_filename, len);
|
||||
backup_type = CORE_BACKUP_TYPE_LIB;
|
||||
break;
|
||||
default:
|
||||
/* Backup is invalid */
|
||||
break;
|
||||
}
|
||||
|
||||
if (core_filename)
|
||||
{
|
||||
free(core_filename);
|
||||
core_filename = NULL;
|
||||
}
|
||||
|
||||
return backup_type;
|
||||
}
|
||||
|
||||
/*************************/
|
||||
/* Backup List Functions */
|
||||
/*************************/
|
||||
|
||||
/**************************************/
|
||||
/* Initialisation / De-Initialisation */
|
||||
/**************************************/
|
||||
|
||||
/* Parses backup file name and adds to backup list, if valid */
|
||||
static bool core_backup_add_entry(core_backup_list_t *backup_list,
|
||||
const char *core_filename, const char *backup_path)
|
||||
{
|
||||
char *backup_filename = NULL;
|
||||
core_backup_list_entry_t *entry = NULL;
|
||||
unsigned backup_mode = 0;
|
||||
|
||||
if (!backup_list ||
|
||||
string_is_empty(core_filename) ||
|
||||
string_is_empty(backup_path) ||
|
||||
(backup_list->size >= backup_list->capacity))
|
||||
goto error;
|
||||
|
||||
backup_filename = strdup(path_basename(backup_path));
|
||||
|
||||
if (string_is_empty(backup_filename))
|
||||
goto error;
|
||||
|
||||
/* Ensure base backup filename matches core */
|
||||
if (!string_starts_with(backup_filename, core_filename))
|
||||
goto error;
|
||||
|
||||
/* Remove backup file extension */
|
||||
path_remove_extension(backup_filename);
|
||||
|
||||
/* Parse backup filename metadata
|
||||
* - <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
175
core_backup.h
Normal 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
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
48
libretro-common/include/time/rtime.h
Normal file
48
libretro-common/include/time/rtime.h
Normal 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
|
@ -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)
|
||||
{
|
||||
|
80
libretro-common/time/rtime.c
Normal file
80
libretro-common/time/rtime.c
Normal 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;
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
|
@ -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];
|
||||
|
@ -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 */
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
27
msg_hash.h
27
msg_hash.h
@ -1392,6 +1392,8 @@ enum msg_hash_enums
|
||||
MENU_ENUM_LABEL_DEFERRED_AUDIO_MIXER_SETTINGS_LIST,
|
||||
MENU_ENUM_LABEL_DEFERRED_CORE_SETTINGS_LIST,
|
||||
MENU_ENUM_LABEL_DEFERRED_CORE_INFORMATION_LIST,
|
||||
MENU_ENUM_LABEL_DEFERRED_CORE_RESTORE_BACKUP_LIST,
|
||||
MENU_ENUM_LABEL_DEFERRED_CORE_DELETE_BACKUP_LIST,
|
||||
MENU_ENUM_LABEL_DEFERRED_USER_BINDS_LIST,
|
||||
MENU_ENUM_LABEL_DEFERRED_ACCOUNTS_CHEEVOS_LIST,
|
||||
MENU_ENUM_LABEL_DEFERRED_ACCOUNTS_TWITCH_LIST,
|
||||
@ -1494,6 +1496,7 @@ enum msg_hash_enums
|
||||
MENU_LABEL(CORE_OPTIONS),
|
||||
MENU_LABEL(NO_SHADER_PARAMETERS),
|
||||
MENU_LABEL(NO_CORE_INFORMATION_AVAILABLE),
|
||||
MENU_LABEL(NO_CORE_BACKUPS_AVAILABLE),
|
||||
MENU_LABEL(NO_CORES_AVAILABLE),
|
||||
|
||||
/* Audio */
|
||||
@ -1936,6 +1939,30 @@ enum msg_hash_enums
|
||||
MSG_ALL_CORES_UPDATED,
|
||||
MSG_NUM_CORES_UPDATED,
|
||||
|
||||
/* Core backup/restore */
|
||||
MENU_LABEL(CORE_CREATE_BACKUP),
|
||||
MENU_LABEL(CORE_RESTORE_BACKUP_LIST),
|
||||
MENU_LABEL(CORE_DELETE_BACKUP_LIST),
|
||||
|
||||
MENU_ENUM_LABEL_CORE_RESTORE_BACKUP_ENTRY,
|
||||
MENU_ENUM_LABEL_CORE_DELETE_BACKUP_ENTRY,
|
||||
MENU_ENUM_LABEL_VALUE_CORE_BACKUP_CRC,
|
||||
|
||||
MSG_CORE_BACKUP_SCANNING_CORE,
|
||||
MSG_CORE_BACKUP_ALREADY_EXISTS,
|
||||
MSG_BACKING_UP_CORE,
|
||||
MSG_CORE_BACKUP_COMPLETE,
|
||||
MSG_CORE_RESTORATION_ALREADY_INSTALLED,
|
||||
MSG_RESTORING_CORE,
|
||||
MSG_CORE_RESTORATION_COMPLETE,
|
||||
MSG_CORE_INSTALLATION_ALREADY_INSTALLED,
|
||||
MSG_INSTALLING_CORE,
|
||||
MSG_CORE_INSTALLATION_COMPLETE,
|
||||
MSG_CORE_RESTORATION_INVALID_CONTENT,
|
||||
MSG_CORE_BACKUP_FAILED,
|
||||
MSG_CORE_RESTORATION_FAILED,
|
||||
MSG_CORE_INSTALLATION_FAILED,
|
||||
|
||||
MENU_LABEL(VIDEO_SHADER_PARAMETERS),
|
||||
MENU_LABEL(VIDEO_SHADER_PRESET_PARAMETERS),
|
||||
MENU_LABEL(DISK_OPTIONS),
|
||||
|
@ -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();
|
||||
|
@ -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(¤t_time);
|
||||
time_info = localtime(¤t_time);
|
||||
|
||||
/* This can actually happen, but if does we probably
|
||||
* have bigger problems to worry about... */
|
||||
if(!time_info)
|
||||
{
|
||||
RARCH_ERR("Failed to get current time.\n");
|
||||
return;
|
||||
}
|
||||
rtime_localtime(¤t_time, &time_info);
|
||||
|
||||
/* Extract values */
|
||||
runtime_log->last_played.year = (unsigned)time_info->tm_year + 1900;
|
||||
runtime_log->last_played.month = (unsigned)time_info->tm_mon + 1;
|
||||
runtime_log->last_played.day = (unsigned)time_info->tm_mday;
|
||||
runtime_log->last_played.hour = (unsigned)time_info->tm_hour;
|
||||
runtime_log->last_played.minute = (unsigned)time_info->tm_min;
|
||||
runtime_log->last_played.second = (unsigned)time_info->tm_sec;
|
||||
runtime_log->last_played.year = (unsigned)time_info.tm_year + 1900;
|
||||
runtime_log->last_played.month = (unsigned)time_info.tm_mon + 1;
|
||||
runtime_log->last_played.day = (unsigned)time_info.tm_mday;
|
||||
runtime_log->last_played.hour = (unsigned)time_info.tm_hour;
|
||||
runtime_log->last_played.minute = (unsigned)time_info.tm_min;
|
||||
runtime_log->last_played.second = (unsigned)time_info.tm_sec;
|
||||
}
|
||||
|
||||
/* Resets log to default (zero) values */
|
||||
|
961
tasks/task_core_backup.c
Normal file
961
tasks/task_core_backup.c
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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,
|
||||
|
@ -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));
|
||||
|
Loading…
x
Reference in New Issue
Block a user