Files
RetroArch/tasks/task_save.c
T
LibretroAdmin 583785e886 save, task_save: bulk writes for save RAM and save states
Save RAM is written in one piece and flushed; a save state in one
piece (the synchronous path) or in 100 KB to 16 MB pieces (the
task).  These uncompressed writers now ask for descriptor writes:
on the desktops and Android the bytes go to the file with no copy
through a stdio buffer, and the save RAM flush has nothing left to
push.  The compressed (rzip) writers are unchanged.  Where descriptor
writes are not available the hint is ignored.
2026-11-11 06:54:15 +01:00

2690 lines
83 KiB
C

/* RetroArch - A frontend for libretro.
* Copyright (C) 2011-2017 - Daniel De Matteis
* Copyright (C) 2016-2019 - Brad Parker
*
* 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 <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <time.h>
#include <compat/strl.h>
#include <lists/string_list.h>
#include <streams/interface_stream.h>
#include <streams/file_stream.h>
#include <streams/rzip_stream.h>
#include <rthreads/rthreads.h>
#include <file/file_path.h>
#include <retro_miscellaneous.h>
#include <retro_timers.h>
#include <features/features_cpu.h>
#include <time/rtime.h>
#ifdef HAVE_CONFIG_H
#include "../config.h"
#endif
#ifdef HAVE_CHEEVOS
#include "../cheevos/cheevos.h"
#endif
#include "../content.h"
#include "../core.h"
#include "../core_info.h"
#include "../file_path_special.h"
#include "../configuration.h"
#include "../audio/audio_driver.h"
#include "../gfx/video_driver.h"
#include "../msg_hash.h"
#include "../runloop.h"
#include "../verbosity.h"
#include "tasks_internal.h"
#ifdef __EMSCRIPTEN__
/* Use huge chunks since each read/write is a possible suspend to
JS code */
#define SAVE_STATE_CHUNK 16 * 1024 * 1024
#else
/* A low common denominator transfer quantum. On a slow
(speed class 6) SD card, we can write 6MB/s. That gives us
roughly 100KB/frame, so a single quantum is one syscall that
fits inside one frame even on the worst storage we support.
This is the size of one read/write call, NOT the amount of work
a tick may do. Tying the two together caps the save/load task
at SAVE_STATE_CHUNK * tick_rate == ~6MB/s no matter what the
device can actually do: measured on NVMe, one 100KB
intfstream_write costs ~62us out of a 16667us frame, so 99.6%
of every frame's budget sits idle and a 16MB state takes
164 ticks (2.7s at 60Hz) to write. The tick budget below is
what bounds a tick now; the quantum only bounds how long the
handler can overshoot that budget, which is why it stays sized
for the slowest device rather than the fastest. */
#define SAVE_STATE_CHUNK 100 * 1024
#endif
/* The quantum for a compressed save: two whole rzip chunks, so each
* write is compressed straight from the state, the two chunks across
* the worker pool, instead of being copied into the stream's buffer
* and compressed one chunk at a time. What it writes to the device is
* the compressed size, which for a savestate is well under the
* uncompressed quantum above. */
#ifdef __EMSCRIPTEN__
/* Already a multiple of the chunk there */
#define SAVE_STATE_RZIP_CHUNK SAVE_STATE_CHUNK
#else
#define SAVE_STATE_RZIP_CHUNK (2 * RZIP_DEFAULT_CHUNK_SIZE)
#endif
/* Wall-clock budget for one save/load tick, in microseconds.
The handler keeps transferring SAVE_STATE_CHUNK quanta until this
is exhausted, then yields. ~12% of a 60Hz frame.
Chosen as a time rather than a byte count because the quantity
that must be bounded is the stall the user sees, and only a clock
measures that; a byte count is a guess about device speed that is
wrong by two orders of magnitude across the range of devices
RetroArch runs on.
The loop is do/while, so exactly one quantum is always
transferred. On a device slow enough that one quantum exceeds
the budget the behaviour is therefore byte-for-byte what it was
before this budget existed - there is no worst case to regress. */
#define SAVE_STATE_TICK_BUDGET_US 2000
#define RASTATE_VERSION 1
#define RASTATE_MEM_BLOCK "MEM "
#define RASTATE_CHEEVOS_BLOCK "ACHV"
#define RASTATE_REPLAY_BLOCK "RPLY"
#define RASTATE_END_BLOCK "END "
struct save_state_buf
{
void* data;
size_t size;
/* Bytes actually allocated at 'data'. Equal to 'size' for every
* buffer that was allocated to fit, and larger only for
* undo_load_buf, which reuses an allocation across snapshots that
* may differ in length. Tracked on all of them so the field
* cannot be read stale by whoever reuses this struct next. */
size_t capacity;
char path[PATH_MAX_LENGTH];
};
struct ram_save_state_buf
{
struct save_state_buf state_buf;
bool to_write_file;
};
struct sram_block
{
void *data;
size_t size;
unsigned type;
};
enum save_task_state_flags
{
SAVE_TASK_FLAG_LOAD_TO_BACKUP_BUFF = (1 << 0),
SAVE_TASK_FLAG_AUTOLOAD = (1 << 1),
SAVE_TASK_FLAG_AUTOSAVE = (1 << 2),
SAVE_TASK_FLAG_UNDO_SAVE = (1 << 3),
SAVE_TASK_FLAG_MUTE = (1 << 4),
SAVE_TASK_FLAG_THUMBNAIL_ENABLE = (1 << 5),
SAVE_TASK_FLAG_HAS_VALID_FB = (1 << 6),
SAVE_TASK_FLAG_COMPRESS_FILES = (1 << 7)
};
typedef struct
{
intfstream_t *file;
void *data;
void *undo_data;
ssize_t size;
ssize_t undo_size;
ssize_t written;
ssize_t bytes_read;
int state_slot;
uint8_t flags;
/* Frontend rastate blocks, captured on the main thread at push for
* the background path: SET_SAVE_STATE_IN_BACKGROUND is the core's
* promise about its own serialize, not about the frontend's replay
* or achievement state, so the worker serializes only the core and
* writes these as captured. */
void *fe_replay;
size_t fe_replay_size;
void *fe_cheevos;
size_t fe_cheevos_size;
/* Captured at push on the main thread: the load handler's
* core-readiness poll reads the frame counter through this,
* never through the video singleton - task workers reach no
* getter. The read stays a single benign per-tick poll of a
* monotonic counter. */
const uint64_t *frame_count;
retro_task_callback_t done_cb; /* the caller's */
char path[PATH_MAX_LENGTH];
} save_task_state_t;
typedef save_task_state_t load_task_data_t;
/* Holds the previous saved state
* Can be restored to disk with undo_save_state(). */
static struct save_state_buf undo_save_buf;
/* Holds the data from before a load_state() operation
* Can be restored with undo_load_state(). */
static struct save_state_buf undo_load_buf;
/* The allocation undo_load_buf handed back last time it was replaced,
* kept rather than freed. Retaking the undo snapshot is the hottest
* serialize in the frontend - content_load_state_cb does it on every
* single state load - and it is a full state-sized allocation each
* time. Keeping one spare and swapping the two means the pages stay
* faulted in; see content_serialize_reusing().
*
* Only ever non-NULL when undo is enabled: content_save_state()
* returns before reaching this whenever save_state_disable_undo is
* set, which is the setting that says memory is scarce, so the spare
* is never held against that user's wishes. */
static struct
{
void *data;
size_t capacity;
} undo_load_spare;
/* Buffer that stores state instead of file.
* This is useful for devices with slow I/O. */
static struct ram_save_state_buf ram_buf;
static bool save_state_in_background = false;
/* See content_load_state_in_progress() for why these are counts and
* not a task_queue_find(). State tasks are TASK_TYPE_BLOCKING, so at
* most one is in flight; the rest of a count is held behind it. */
static unsigned load_state_tasks_pending = 0;
/* How long a state load waiting for the core's second frame parks. */
#define LOAD_STATE_FRAME_WAIT_USEC 8000
static unsigned save_state_tasks_pending = 0;
static bool save_state_disable_undo = false;
/* State tasks the queue turned away while another state task ran,
* held here in order and pushed as each one ahead of them finishes.
* Dropped, Load State pressed while a save was still writing did
* nothing at all, nor Save State during a load or an automatic save.
* Frame thread only: every state push and every state callback runs
* there. Bounded: past it a request is refused, as all were before. */
#define STATE_TASKS_HELD_MAX 4
static retro_task_t *state_tasks_held[STATE_TASKS_HELD_MAX];
static unsigned state_tasks_held_count = 0;
/* Pushes @task, or holds it behind the state task in flight; false
* only when the hold is full. Anything already held goes first. */
static bool state_task_push(retro_task_t *task)
{
if (!state_tasks_held_count && task_queue_push(task))
return true;
if (state_tasks_held_count >= STATE_TASKS_HELD_MAX)
return false;
state_tasks_held[state_tasks_held_count++] = task;
return true;
}
/* As state_task_push(), ahead of anything held: a callback chaining
* its own next task, the save after its backup load. */
static bool state_task_push_first(retro_task_t *task)
{
unsigned i;
if (task_queue_push(task))
return true;
if (state_tasks_held_count >= STATE_TASKS_HELD_MAX)
return false;
for (i = state_tasks_held_count; i > 0; i--)
state_tasks_held[i] = state_tasks_held[i - 1];
state_tasks_held[0] = task;
state_tasks_held_count++;
return true;
}
/* At the end of a state task's callback: the next held one goes in. */
static void state_tasks_release(void)
{
unsigned i;
if ( !state_tasks_held_count
|| !task_queue_push(state_tasks_held[0]))
return;
for (i = 1; i < state_tasks_held_count; i++)
state_tasks_held[i - 1] = state_tasks_held[i];
state_tasks_held[--state_tasks_held_count] = NULL;
}
/* Time tracking for automatic savestate interval */
static retro_time_t last_savestate_automatic_time = 0;
typedef struct rastate_size_info
{
size_t total_size;
size_t coremem_size;
#ifdef HAVE_CHEEVOS
size_t cheevos_size;
#endif
#ifdef HAVE_BSV_MOVIE
size_t replay_size;
#endif
} rastate_size_info_t;
/**
* undo_load_state:
* Revert to the state before a state was loaded.
*
* Returns: true if successful, false otherwise.
**/
bool content_undo_load_state(void)
{
unsigned i;
bool ret = false;
bool captured = false;
bool ramped = false;
unsigned num_blocks = 0;
void *restore_data = NULL;
size_t restore_size = 0;
size_t restore_cap = 0;
struct sram_block *blocks = NULL;
struct string_list *savefile_list = (struct string_list*)savefile_ptr_get();
/* The undo buffer holds a state this core produced during this
* session - its existence outranks (possibly stale) metadata. */
if ( !core_info_current_supports_savestate()
&& !undo_load_buf.data)
{
RARCH_LOG("[State] %s\n",
msg_hash_to_str(MSG_CORE_DOES_NOT_SUPPORT_SAVESTATES));
return false;
}
RARCH_LOG("[State] %s \"%s\", %u %s.\n",
msg_hash_to_str(MSG_LOADING_STATE),
undo_load_buf.path,
(unsigned)undo_load_buf.size,
msg_hash_to_str(MSG_BYTES));
/* TODO/FIXME - This checking of SRAM overwrite,
* the backing up of it and
* its flushing could all be in their
* own functions... */
if ( savefile_list
&& savefile_list->size
&& config_get_ptr()->bools.block_sram_overwrite)
{
RARCH_LOG("[SRAM] %s.\n",
msg_hash_to_str(MSG_BLOCKING_SRAM_OVERWRITE));
if ((blocks = (struct sram_block*)
calloc(savefile_list->size, sizeof(*blocks))))
{
num_blocks = (unsigned)savefile_list->size;
for (i = 0; i < num_blocks; i++)
blocks[i].type = savefile_list->elems[i].attr.i;
}
}
for (i = 0; i < num_blocks; i++)
{
retro_ctx_memory_info_t mem_info;
mem_info.id = blocks[i].type;
core_get_memory(&mem_info);
blocks[i].size = mem_info.size;
}
for (i = 0; i < num_blocks; i++)
if (blocks[i].size)
blocks[i].data = malloc(blocks[i].size);
/* Backup current SRAM which is overwritten by unserialize. */
for (i = 0; i < num_blocks; i++)
{
if (blocks[i].data)
{
retro_ctx_memory_info_t mem_info;
const void *ptr = NULL;
mem_info.id = blocks[i].type;
core_get_memory(&mem_info);
if ((ptr = mem_info.data))
memcpy(blocks[i].data, ptr, blocks[i].size);
}
}
/* The state about to be restored lives in undo_load_buf, and the
* capture below overwrites undo_load_buf - so the bytes being
* restored must be kept out of its reach. Detaching the
* allocation does that for free: after the detach undo_load_buf
* owns no buffer, so the capture serializes into the spare and
* swaps that in, and neither one can touch the bytes being
* restored. */
restore_data = undo_load_buf.data;
restore_size = undo_load_buf.size;
restore_cap = undo_load_buf.capacity;
undo_load_buf.data = NULL;
undo_load_buf.size = 0;
undo_load_buf.capacity = 0;
/* An undo jumps the game's state exactly as a load does. See
* audio_driver_jump_fade_begin(). */
ramped = audio_driver_jump_fade_begin();
/* Swap the current state with the backup state. This way, we can undo
what we're undoing */
captured = content_save_state("RAM", false);
ret = content_deserialize_state(restore_data, restore_size);
audio_driver_jump_fade_end(ramped);
if (captured)
{
/* The detached allocation has no owner now. Hand it to the
* spare rather than freeing it, so the next capture finds a
* buffer that is still mapped instead of asking the kernel for
* a fresh one - the same trade the capture path makes.
*
* The spare is empty here: the capture swapped undo_load_buf's
* buffer into it, and the detach above left that NULL. The
* free is kept so this does not become a leak if that ever
* stops being true. */
free(undo_load_spare.data);
undo_load_spare.data = restore_data;
undo_load_spare.capacity = restore_cap;
}
else
{
/* The capture failed, so there is nothing to undo back to. Put
* the snapshot back where it was, which is where the
* copy-based form left it in this case. */
undo_load_buf.data = restore_data;
undo_load_buf.size = restore_size;
undo_load_buf.capacity = restore_cap;
}
/* Flush back. */
for (i = 0; i < num_blocks; i++)
{
if (blocks[i].data)
{
retro_ctx_memory_info_t mem_info;
void *ptr = NULL;
mem_info.id = blocks[i].type;
core_get_memory(&mem_info);
if ((ptr = mem_info.data))
memcpy(ptr, blocks[i].data, blocks[i].size);
}
}
for (i = 0; i < num_blocks; i++)
{
free(blocks[i].data);
blocks[i].data = NULL;
}
free(blocks);
if (!ret)
{
RARCH_ERR("[State] %s \"%s\".\n",
msg_hash_to_str(MSG_FAILED_TO_UNDO_LOAD_STATE),
undo_load_buf.path);
return false;
}
return true;
}
static void undo_save_state_cb(retro_task_t *task,
void *task_data,
void *user_data, const char *error)
{
save_task_state_t *state = (save_task_state_t*)task_data;
if (save_state_tasks_pending)
save_state_tasks_pending--;
/* Wipe the save file buffer as it's intended to be one use only */
undo_save_buf.path[0] = '\0';
undo_save_buf.size = 0;
undo_save_buf.capacity = 0;
if (undo_save_buf.data)
{
free(undo_save_buf.data);
undo_save_buf.data = NULL;
}
free(state);
state_tasks_release();
}
bool content_finish_tmp(const char *path, bool ok)
{
int ret = filestream_finish_atomic(path, ok);
if (ret == 1 && path_is_valid(path))
{
char tmp_path[PATH_MAX_LENGTH];
if (filestream_atomic_temp_name(tmp_path, sizeof(tmp_path), path))
filestream_delete(tmp_path);
}
return ret == 0;
}
/**
* task_save_handler_finished:
* @task : the task to finish
* @state : the state associated with this task
*
* Close the save state file and finish the task.
**/
static void task_save_handler_finished(retro_task_t *task,
save_task_state_t *state)
{
uint8_t flg;
task_set_flags(task, RETRO_TASK_FLG_FINISHED, true);
/* NULL when the handler failed before the file was opened
* (serialize failure, or the open itself). */
if (state->file)
{
/* rzip writes its last chunk at close, and a buffered write
* reports a full disk there, so a failed close is a failed save
* and the temporary file must not replace the old state. */
if ( intfstream_close(state->file) != 0
&& !task_get_error(task))
task_set_error(task, strdup(
msg_hash_to_str(MSG_FAILED_TO_SAVE_STATE_TO)));
free(state->file);
state->file = NULL;
}
flg = task_get_flags(task);
if (!task_get_error(task) && ((flg & RETRO_TASK_FLG_CANCELLED) > 0))
task_set_error(task, strdup("Task canceled"));
/* The state was written to "<path>.tmp", so a crash, cancel or
* short write leaves the existing file intact. Move it into place
* on success, discard it otherwise. */
{
char tmp_path[PATH_MAX_LENGTH];
if (!filestream_atomic_temp_name(tmp_path, sizeof(tmp_path), state->path))
{
/* Nothing was written: the open refused the same path. */
if (!task_get_error(task))
task_set_error(task, strdup(
msg_hash_to_str(MSG_FAILED_TO_SAVE_STATE_TO)));
}
else if (!content_finish_tmp(state->path, !task_get_error(task))
&& !task_get_error(task))
task_set_error(task, strdup(
msg_hash_to_str(MSG_FAILED_TO_SAVE_STATE_TO)));
}
if (state->data)
{
if ( (state->flags & SAVE_TASK_FLAG_UNDO_SAVE)
&& (state->data == undo_save_buf.data))
undo_save_buf.data = NULL;
free(state->data);
state->data = NULL;
}
free(state->fe_replay);
free(state->fe_cheevos);
state->fe_replay = NULL;
state->fe_cheevos = NULL;
/* The state itself is the callback's task_data, which frees it:
* nothing is allocated here, so the callback always has it. */
task_set_data(task, state);
}
/* Align to 8-byte boundary */
#define CONTENT_ALIGN_SIZE(size) ((((size) + 7) & ~7))
/* Zero only the alignment padding bytes after a block's data payload.
* When a block's unaligned size is not a multiple of 8, there are up to
* 7 padding bytes that would otherwise contain uninitialized data,
* causing nondeterministic compressed state file sizes. */
#define CONTENT_ZERO_PADDING(output, unaligned_size) \
do { \
size_t _pad = CONTENT_ALIGN_SIZE(unaligned_size) \
- (unaligned_size); \
if (_pad > 0) \
memset((output) + (unaligned_size), 0, _pad); \
} while (0)
/* Frontend blocks pre-captured on the main thread; NULL means read
* them live, which is only the main thread's to do. */
typedef struct rastate_captured
{
const void *replay;
size_t replay_size;
const void *cheevos;
size_t cheevos_size;
} rastate_captured_t;
static size_t content_get_rastate_size(rastate_size_info_t* size, bool rewind)
{
size_t info_size = core_serialize_size();
if (!info_size)
return 0;
size->coremem_size = info_size;
/* 8-byte identifier, 8-byte block header, content, 8-byte terminator */
size->total_size = 8 + 8 + CONTENT_ALIGN_SIZE(info_size) + 8;
#ifdef HAVE_CHEEVOS
/* 8-byte block header + content */
if ((size->cheevos_size = rcheevos_get_serialize_size()) > 0)
size->total_size += 8 + CONTENT_ALIGN_SIZE(size->cheevos_size);
#endif
#ifdef HAVE_BSV_MOVIE
/* 8-byte block header + content */
if (!rewind)
{
size->replay_size = replay_get_serialize_size(input_state_get_ptr());
if (size->replay_size > 0)
size->total_size += 8 + CONTENT_ALIGN_SIZE(size->replay_size);
}
else
size->replay_size = 0;
#endif
return size->total_size;
}
/* The worker's sizing: the core live - its serialize is what the
* core's background request vouches for - and the frontend's blocks
* from the capture, with no live frontend read on this path at all. */
static size_t content_get_rastate_size_captured(rastate_size_info_t* size,
const rastate_captured_t *captured)
{
size_t info_size = core_serialize_size();
if (!info_size)
return 0;
size->coremem_size = info_size;
size->total_size = 8 + 8 + CONTENT_ALIGN_SIZE(info_size) + 8;
#ifdef HAVE_CHEEVOS
size->cheevos_size = captured->cheevos_size;
if (size->cheevos_size > 0)
size->total_size += 8 + CONTENT_ALIGN_SIZE(size->cheevos_size);
#endif
#ifdef HAVE_BSV_MOVIE
size->replay_size = captured->replay_size;
if (size->replay_size > 0)
size->total_size += 8 + CONTENT_ALIGN_SIZE(size->replay_size);
#endif
(void)captured;
return size->total_size;
}
size_t content_get_serialized_size(void)
{
rastate_size_info_t size;
return content_get_rastate_size(&size, false);
}
size_t content_get_serialized_size_rewind(void)
{
rastate_size_info_t size;
return content_get_rastate_size(&size, true);
}
static void content_write_block_header(unsigned char* output, const char* header, size_t len)
{
memcpy(output, header, 4);
output[4] = ((len) & 0xFF);
output[5] = ((len >> 8) & 0xFF);
output[6] = ((len >> 16) & 0xFF);
output[7] = ((len >> 24) & 0xFF);
}
static bool content_write_serialized_state(void* buffer,
rastate_size_info_t* size,
bool rewind)
{
retro_ctx_serialize_info_t serial_info;
unsigned char* output = (unsigned char*)buffer;
/* 8-byte identifier "RASTATE1" where 1 is the version */
memcpy(output, "RASTATE", 7);
output[7] = RASTATE_VERSION;
output += 8;
/* Replay block---this has to come before the mem block since its
contents may prevent the state from loading (e.g., if it's
incompatible with the current recording). */
#ifdef HAVE_BSV_MOVIE
{
input_driver_state_t *input_st = input_state_get_ptr();
#ifdef HAVE_REWIND
bool frame_is_reversed = state_manager_frame_is_reversed();
#else
bool frame_is_reversed = false;
#endif
if ( !rewind
&& size->replay_size
&& input_st->bsv_movie_state.flags & (BSV_FLAG_MOVIE_RECORDING | BSV_FLAG_MOVIE_PLAYBACK)
&& !frame_is_reversed)
{
content_write_block_header(output,
RASTATE_REPLAY_BLOCK, size->replay_size);
if (replay_get_serialized_data(input_st, output + 8))
{
CONTENT_ZERO_PADDING(output + 8, size->replay_size);
output += CONTENT_ALIGN_SIZE(size->replay_size) + 8;
}
}
}
#endif
/* important - write the unaligned size - some cores fail if they aren't passed the exact right size. */
content_write_block_header(output, RASTATE_MEM_BLOCK, size->coremem_size);
output += 8;
/* important - pass the unaligned size to the core. some fail if it isn't exactly what they're expecting. */
serial_info.size = size->coremem_size;
serial_info.data = (void*)output;
if (!core_serialize(&serial_info))
return false;
CONTENT_ZERO_PADDING(output, size->coremem_size);
output += CONTENT_ALIGN_SIZE(size->coremem_size);
#ifdef HAVE_CHEEVOS
if (size->cheevos_size)
{
content_write_block_header(output,
RASTATE_CHEEVOS_BLOCK, size->cheevos_size);
if (rcheevos_get_serialized_data(output + 8))
{
CONTENT_ZERO_PADDING(output + 8, size->cheevos_size);
output += CONTENT_ALIGN_SIZE(size->cheevos_size) + 8;
}
}
#endif
content_write_block_header(output, RASTATE_END_BLOCK, 0);
return true;
}
/* The worker's writer: the frontend blocks come from the push-time
* capture and the core serializes live. No live frontend read exists
* on this path - the split is what the thread audit holds. */
static bool content_write_serialized_state_captured(void* buffer,
rastate_size_info_t* size, const rastate_captured_t *captured)
{
retro_ctx_serialize_info_t serial_info;
unsigned char* output = (unsigned char*)buffer;
memcpy(output, "RASTATE", 7);
output[7] = RASTATE_VERSION;
output += 8;
#ifdef HAVE_BSV_MOVIE
if (captured->replay && size->replay_size > 0)
{
content_write_block_header(output,
RASTATE_REPLAY_BLOCK, size->replay_size);
memcpy(output + 8, captured->replay, size->replay_size);
CONTENT_ZERO_PADDING(output + 8, size->replay_size);
output += CONTENT_ALIGN_SIZE(size->replay_size) + 8;
}
#endif
content_write_block_header(output, RASTATE_MEM_BLOCK, size->coremem_size);
output += 8;
serial_info.size = size->coremem_size;
serial_info.data = (void*)output;
if (!core_serialize(&serial_info))
return false;
CONTENT_ZERO_PADDING(output, size->coremem_size);
output += CONTENT_ALIGN_SIZE(size->coremem_size);
#ifdef HAVE_CHEEVOS
if (captured->cheevos && size->cheevos_size > 0)
{
content_write_block_header(output,
RASTATE_CHEEVOS_BLOCK, size->cheevos_size);
memcpy(output + 8, captured->cheevos, size->cheevos_size);
CONTENT_ZERO_PADDING(output + 8, size->cheevos_size);
output += CONTENT_ALIGN_SIZE(size->cheevos_size) + 8;
}
#endif
content_write_block_header(output, RASTATE_END_BLOCK, 0);
return true;
}
bool content_serialize_state_rewind(void* buffer, size_t buffer_size)
{
rastate_size_info_t size;
size_t _len = content_get_rastate_size(&size, true);
if (_len == 0)
return false;
if (_len > buffer_size)
{
#ifdef DEBUG
static size_t last_reported_len = 0;
if (_len != last_reported_len)
{
last_reported_len = _len;
RARCH_WARN("[Rewind] State size exceeds frame size (%zu > %zu).\n", _len, buffer_size);
}
#endif
return false;
}
return content_write_serialized_state(buffer, &size, true);
}
static void *content_get_serialized_data(size_t *serial_size)
{
size_t _len;
void* data;
rastate_size_info_t size;
if ((_len = content_get_rastate_size(&size, false)) == 0)
return NULL;
/* Alignment padding bytes are zeroed selectively by
* CONTENT_ZERO_PADDING() in content_write_serialized_state(),
* so a full calloc() zero-fill is no longer needed here. */
if (!(data = malloc(_len)))
return NULL;
if (!content_write_serialized_state(data, &size, false))
{
free(data);
return NULL;
}
*serial_size = size.total_size;
return data;
}
/**
* content_serialize_reusing:
* @buf : in/out, the caller's retained buffer. May point to NULL.
* @cap : in/out, bytes currently allocated at *buf.
*
* Serializes the current state into a buffer the caller keeps across
* calls, allocating only when what is there is too small.
*
* Worth the extra bookkeeping because a state-sized allocation is
* served by mmap rather than out of the heap's free lists, so free()
* hands the pages straight back to the kernel and the next serialize
* faults every one of them in again on first touch. That fault
* traffic is not a rounding error next to the copy it exists to hold:
* measured against a 16 MiB state, a fresh malloc plus the copy costs
* 5.08ms where the copy into a retained buffer costs 1.88ms, so 63%
* of a serialize is spent obtaining memory rather than filling it.
* A 4 MiB state measures 72%.
*
* @return the number of bytes serialized, or 0 on failure. On
* failure *buf and *cap remain valid (possibly reallocated), so the
* caller can retry into them; the contents are undefined.
**/
static size_t content_serialize_reusing(void **buf, size_t *cap)
{
size_t _len;
rastate_size_info_t size;
if ((_len = content_get_rastate_size(&size, false)) == 0)
return 0;
if (*cap < _len)
{
void *grown;
/* free()+malloc() rather than realloc(): every byte is about to
* be overwritten, and realloc would copy the old contents into
* the new allocation first. */
free(*buf);
if (!(grown = malloc(_len)))
{
*buf = NULL;
*cap = 0;
return 0;
}
*buf = grown;
*cap = _len;
}
/* Alignment padding is zeroed selectively by CONTENT_ZERO_PADDING()
* in content_write_serialized_state(), so a reused buffer needs no
* scrub: every byte the reader can reach is written by the write
* below, exactly as in the freshly-malloc'd case. */
if (!content_write_serialized_state(*buf, &size, false))
return 0;
return size.total_size;
}
/**
* task_save_handler:
* @task : the task being worked on
*
* Write a chunk of data to the save state file.
**/
static void task_save_handler(retro_task_t *task)
{
uint8_t flg;
ssize_t remaining;
int written = 0;
save_task_state_t *state = (save_task_state_t*)task->state;
/* Serialize before opening, not after. Opening first meant a
* failed serialize had already created (and truncated) the target,
* so the slot was left holding a zero-byte file. */
if (!state->data)
{
size_t _len = 0;
rastate_size_info_t size;
rastate_captured_t captured;
captured.replay = state->fe_replay;
captured.replay_size = state->fe_replay_size;
captured.cheevos = state->fe_cheevos;
captured.cheevos_size = state->fe_cheevos_size;
state->size = 0;
if ((_len = content_get_rastate_size_captured(&size, &captured)) > 0)
{
if ((state->data = malloc(_len)))
{
if (!content_write_serialized_state_captured(state->data,
&size, &captured))
{
free(state->data);
state->data = NULL;
}
else
/* size is filled exactly when the sizing call above
* succeeded; the other arms leave it at its initial
* zero rather than reading it. */
state->size = (ssize_t)size.total_size;
}
}
/* A failed serialize must be failed here: with data NULL and
* size 0, every test below reads as success - remaining is 0,
* so written == remaining and written == size - and the
* handler reports a COMPLETED save of a zero-byte file, giving
* the user a 'state saved' notification and an empty slot. */
if (!state->data || state->size <= 0)
{
RARCH_ERR("[State] Save task could not serialize core state "
"for slot %d, path \"%s\".\n",
state->state_slot, state->path);
task_set_error(task, strdup(
msg_hash_to_str(MSG_FAILED_TO_SAVE_STATE_TO)));
task_save_handler_finished(task, state);
return;
}
}
if (!state->file)
{
char tmp_path[PATH_MAX_LENGTH];
/* Write to "<path>.tmp"; task_save_handler_finished moves it
* into place on success or deletes it on failure or cancel.
* A path too long to take the suffix is refused rather than
* written in place. */
if (!filestream_atomic_temp_name(tmp_path, sizeof(tmp_path), state->path))
state->file = NULL;
else if (state->flags & SAVE_TASK_FLAG_COMPRESS_FILES)
state->file = intfstream_open_rzip_file(
tmp_path, RETRO_VFS_FILE_ACCESS_WRITE);
else
state->file = intfstream_open_file(
tmp_path, RETRO_VFS_FILE_ACCESS_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_SEQUENTIAL_BULK);
if (!state->file)
{
/* This must fail the task, not bare-return: a task neither
* errored nor finished is re-entered on the next tick and
* retries the open forever - and because save tasks are
* TASK_TYPE_BLOCKING, that one task wedges every other
* blocking task for the rest of the session. An open that
* failed once (read-only medium, bad path, no space) is not
* going to succeed on retry; fail it. */
RARCH_ERR("[State] Save task could not open \"%s\" for writing "
"(slot %d). The auto-index slot was already advanced, so "
"this leaves an advanced slot with no save file.\n",
state->path, state->state_slot);
task_set_error(task, strdup(
msg_hash_to_str(MSG_FAILED_TO_SAVE_STATE_TO)));
task_save_handler_finished(task, state);
return;
}
}
/* Transfer quanta until the tick budget is spent. do/while, so a
* device slow enough that one quantum exceeds the budget behaves
* exactly as it did when a tick was hardcoded to one quantum. */
{
retro_time_t deadline = cpu_features_get_time_usec()
+ SAVE_STATE_TICK_BUDGET_US;
do
{
remaining = MIN(state->size - state->written,
(state->flags & SAVE_TASK_FLAG_COMPRESS_FILES)
? SAVE_STATE_RZIP_CHUNK : SAVE_STATE_CHUNK);
written = (int)intfstream_write(state->file,
(uint8_t*)state->data + state->written, remaining);
if (written != remaining)
break;
state->written += written;
} while ( state->written < state->size
&& cpu_features_get_time_usec() < deadline);
}
task_set_progress(task, (state->written / (float)state->size) * 100);
flg = task_get_flags(task);
if (((flg & RETRO_TASK_FLG_CANCELLED) > 0) || written != remaining)
{
char msg[128];
if (state->flags & SAVE_TASK_FLAG_UNDO_SAVE)
{
const char *failed_undo_str = msg_hash_to_str(
MSG_FAILED_TO_UNDO_SAVE_STATE);
RARCH_ERR("[State] %s \"%s\".\n", failed_undo_str,
undo_save_buf.path);
snprintf(msg, sizeof(msg), "%s \"RAM\".", failed_undo_str);
}
else
{
size_t _len = strlcpy(msg,
msg_hash_to_str(MSG_FAILED_TO_SAVE_STATE_TO),
sizeof(msg) - 2);
msg[ _len] = ' ';
msg[++_len] = '\0';
strlcpy(msg + _len, state->path, sizeof(msg) - _len);
}
task_set_error(task, strdup(msg));
RARCH_ERR("[State] Save task FAILED for slot %d, path \"%s\" "
"(wrote %d of %d bytes%s).\n",
state->state_slot, state->path,
(int)state->written, (int)state->size,
((flg & RETRO_TASK_FLG_CANCELLED) > 0) ? ", cancelled" : "");
task_save_handler_finished(task, state);
}
else if (state->written == state->size)
{
char *msg = NULL;
RARCH_LOG("[State] Save task COMPLETED for slot %d, path \"%s\" "
"(%d bytes).\n",
state->state_slot, state->path, (int)state->size);
task_free_title(task);
if (state->flags & SAVE_TASK_FLAG_UNDO_SAVE)
msg = strdup(msg_hash_to_str(MSG_RESTORED_OLD_SAVE_STATE));
else if (state->state_slot < 0)
msg = strdup(msg_hash_to_str(MSG_SAVED_STATE_TO_SLOT_AUTO));
else
{
char new_msg[128];
snprintf(new_msg, sizeof(new_msg),
msg_hash_to_str(MSG_SAVED_STATE_TO_SLOT),
state->state_slot);
msg = strdup(new_msg);
}
if (!((flg & RETRO_TASK_FLG_MUTE) > 0) && msg)
{
task_set_title(task, msg);
msg = NULL;
}
task_save_handler_finished(task, state);
if (msg)
free(msg);
}
}
/**
* task_push_undo_save_state:
* @path : file path of the save state
* @data : the save state data to write
* @size : the total size of the save state
*
* Create a new task to undo the last save of the content state.
**/
static bool task_push_undo_save_state(const char *path, void *data, size_t len)
{
retro_task_t *task = task_init();
save_task_state_t *state = (save_task_state_t*)
calloc(1, sizeof(*state));
if (task && state)
{
settings_t *settings = config_get_ptr();
strlcpy(state->path, path, sizeof(state->path));
state->data = data;
state->size = len;
state->flags |= SAVE_TASK_FLAG_UNDO_SAVE;
state->state_slot = settings->ints.state_slot;
if (video_driver_cached_frame_is_hw_render())
state->flags |= SAVE_TASK_FLAG_HAS_VALID_FB;
/* The data is the file the save replaced, byte for byte -
* compressed or not, as it was: written back as it is */
if (!settings->bools.notification_show_save_state)
state->flags |= SAVE_TASK_FLAG_MUTE;
task->type = TASK_TYPE_BLOCKING;
task->state = state;
task->handler = task_save_handler;
task->callback = undo_save_state_cb;
task->title = strdup(msg_hash_to_str(MSG_UNDOING_SAVE_STATE));
if (state->flags & SAVE_TASK_FLAG_MUTE)
task->flags |= RETRO_TASK_FLG_MUTE;
else
task->flags &= ~RETRO_TASK_FLG_MUTE;
if (state_task_push(task))
{
save_state_tasks_pending++;
return true;
}
/* The hold is full: this one never runs, so nothing else frees
* what it holds */
task_free_title(task);
free(task);
task = NULL;
}
if (data)
free(data);
if (state)
free(state);
if (task)
free(task);
return false;
}
/**
* undo_save_state:
* Reverts the last save operation
*
* Returns: true if successful, false otherwise.
**/
bool content_undo_save_state(void)
{
if (core_info_current_supports_savestate())
return task_push_undo_save_state(
undo_save_buf.path,
undo_save_buf.data,
undo_save_buf.size);
RARCH_LOG("[State] %s\n",
msg_hash_to_str(MSG_CORE_DOES_NOT_SUPPORT_SAVESTATES));
return false;
}
/**
* task_load_handler_finished:
* @task : the task to finish
* @state : the state associated with this task
*
* Close the loaded state file and finish the task.
**/
static void task_load_handler_finished(retro_task_t *task,
save_task_state_t *state)
{
uint8_t flg;
task_set_flags(task, RETRO_TASK_FLG_FINISHED, true);
if (state->file)
{
intfstream_close(state->file);
free(state->file);
state->file = NULL;
}
flg = task_get_flags(task);
if (!task_get_error(task) && ((flg & RETRO_TASK_FLG_CANCELLED) > 0))
task_set_error(task, strdup("Task canceled"));
/* The state, with the data read into it, is the callback's
* task_data, which frees both: nothing is allocated here, so the
* callback always has it. */
task_set_data(task, state);
}
/**
* task_load_handler:
* @task : the task being worked on
*
* Load a chunk of data from the save state file.
**/
static void task_load_handler(retro_task_t *task)
{
uint8_t flg;
ssize_t remaining, bytes_read;
save_task_state_t *state = (save_task_state_t*)task->state;
/* A core takes a state from its second frame on (Dolphin's CLI
* autoload reaches here sooner). frame_count is advanced by the
* frame loop, which wakes no task, so the handler looks again after
* about a frame; the unthreaded queue looks once per check. */
if (*state->frame_count < 2)
{
task_queue_park(task, LOAD_STATE_FRAME_WAIT_USEC);
return;
}
if (!state->file)
{
#if defined(HAVE_COMPRESSION)
/* The backup a save takes for undo is the file as it is, and an
* undo puts it back as it was: nothing to inflate now, nor to
* deflate again then */
if (state->flags & SAVE_TASK_FLAG_LOAD_TO_BACKUP_BUFF)
state->file = intfstream_open_file(state->path,
RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
/* Otherwise always use RZIP interface when reading state
* files - this will automatically handle uncompressed
* data */
else
state->file = intfstream_open_rzip_file(state->path,
RETRO_VFS_FILE_ACCESS_READ);
if (!state->file)
goto not_found;
#else
if (!(state->file = intfstream_open_file(state->path,
RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
goto not_found;
#endif
if ((state->size = intfstream_get_size(state->file)) < 0)
goto end;
if (!(state->data = malloc(state->size + 1)))
goto end;
}
#ifdef HAVE_CHEEVOS
if (rcheevos_hardcore_active())
task_set_flags(task, RETRO_TASK_FLG_CANCELLED, true);
#endif
/* Same budgeted-quanta scheme as the save handler; see the comment
* on SAVE_STATE_TICK_BUDGET_US. */
{
retro_time_t deadline = cpu_features_get_time_usec()
+ SAVE_STATE_TICK_BUDGET_US;
do
{
/* A compressed file in whole chunks, inflated straight
* into the state; what is read from the device is the
* compressed size */
remaining = MIN(state->size - state->bytes_read,
intfstream_is_compressed(state->file)
? SAVE_STATE_RZIP_CHUNK : SAVE_STATE_CHUNK);
bytes_read = intfstream_read(state->file,
(uint8_t*)state->data + state->bytes_read, remaining);
if (bytes_read != remaining)
break;
state->bytes_read += bytes_read;
} while ( state->bytes_read < state->size
&& cpu_features_get_time_usec() < deadline);
}
if (state->size > 0)
task_set_progress(task, (state->bytes_read / (float)state->size) * 100);
flg = task_get_flags(task);
if (((flg & RETRO_TASK_FLG_CANCELLED) > 0) || bytes_read != remaining)
{
if (state->flags & SAVE_TASK_FLAG_AUTOLOAD)
{
char msg[128];
snprintf(msg, sizeof(msg),
msg_hash_to_str(MSG_AUTOLOADING_SAVESTATE_FAILED),
path_basename(state->path));
task_set_error(task, strdup(msg));
}
else
task_set_error(task, strdup(msg_hash_to_str(MSG_FAILED_TO_LOAD_STATE)));
free(state->data);
state->data = NULL;
task_load_handler_finished(task, state);
return;
}
if (state->bytes_read == state->size)
{
task_free_title(task);
if (!((flg & RETRO_TASK_FLG_MUTE) > 0))
{
char msg[128];
if (state->flags & SAVE_TASK_FLAG_AUTOLOAD)
snprintf(msg, sizeof(msg),
msg_hash_to_str(MSG_AUTOLOADING_SAVESTATE_SUCCEEDED),
path_basename(state->path));
else
{
if (state->state_slot < 0)
strlcpy(msg,
msg_hash_to_str(MSG_LOADED_STATE_FROM_SLOT_AUTO),
sizeof(msg));
else
snprintf(msg, sizeof(msg),
msg_hash_to_str(MSG_LOADED_STATE_FROM_SLOT),
state->state_slot);
}
task_set_title(task, strdup(msg));
}
task_load_handler_finished(task, state);
}
return;
not_found:
{
char msg[128];
snprintf(msg, sizeof(msg), "%s \"%s\".",
msg_hash_to_str(MSG_FAILED_TO_LOAD_STATE),
path_basename(state->path));
task_set_error(task, strdup(msg));
}
end:
task_load_handler_finished(task, state);
}
static bool content_load_rastate1(unsigned char* input, size_t len)
{
unsigned char *stop = input + len;
bool seen_core = false;
#ifdef HAVE_CHEEVOS
bool seen_cheevos = false;
#endif
#ifdef HAVE_BSV_MOVIE
bool seen_replay = false;
#endif
/* The identifier itself must be present before anything is read
* past it. */
if (len < 8)
return false;
input += 8;
/* input + 8 <= stop, not input < stop. The old bound let the loop
* body run with as little as one byte remaining and then read the
* block length out of input[4..7] - up to six bytes past the end of
* the buffer. ASan reports it for any state file truncated to
* 9..15 bytes, which is not a theoretical shape: savestates get
* shared, synced and copied off failing media, and a short read in
* the load handler produces exactly this buffer. */
while (input + 8 <= stop)
{
/* Assembled through uint32_t, not int. input[7] is an unsigned
* char, which promotes to int, and int << 24 is undefined for
* any top byte >= 0x80 - that is every block length at or above
* 2 GB, which is exactly the range a corrupt or hostile length
* lands in. UBSan reports it; a compiler is entitled to do
* worse than report it, and the value it produces is the one
* the bound check below is asked to trust. */
size_t block_size = (size_t)( ((uint32_t)input[7] << 24)
| ((uint32_t)input[6] << 16)
| ((uint32_t)input[5] << 8)
| (uint32_t)input[4]);
unsigned char *marker = input;
size_t avail;
size_t advance;
input += 8;
/* Terminator carries no payload; check it before the extent
* test so a well-formed END block is not rejected. */
if (memcmp(marker, RASTATE_END_BLOCK, 4) == 0)
break;
/* Nothing below may consume more than the buffer holds. This
* is what stops a corrupt or hostile 32-bit length from being
* handed to core_unserialize / rcheevos_set_serialized_data /
* replay_set_serialized_data as the size of a buffer that is
* not that large - the block contents are attacker-controlled
* in any file that arrived from outside. */
avail = (size_t)(stop - input);
if (block_size > avail)
{
RARCH_ERR("[State] Block \"%.4s\" declares %u bytes with %u "
"left in the state; refusing.\n",
(const char*)marker, (unsigned)block_size,
(unsigned)avail);
return false;
}
/* Padding is written for every block but the last one may sit
* flush against the end of the buffer, so clamp rather than
* reject. Computed after the bound check above: block_size is
* a 32-bit quantity and CONTENT_ALIGN_SIZE would overflow a
* 32-bit size_t near UINT32_MAX. */
if ((advance = CONTENT_ALIGN_SIZE(block_size)) > avail)
advance = avail;
if (memcmp(marker, RASTATE_MEM_BLOCK, 4) == 0)
{
retro_ctx_serialize_info_t serial_info;
serial_info.data_const = (void*)input;
serial_info.size = block_size;
#ifdef HAVE_BSV_MOVIE
{
input_driver_state_t *input_st = input_state_get_ptr();
#ifdef HAVE_REWIND
bool frame_is_reversed = state_manager_frame_is_reversed();
#else
bool frame_is_reversed = false;
#endif
if (BSV_MOVIE_IS_RECORDING() && !seen_replay && !frame_is_reversed)
{
/* TODO OSD message */
RARCH_ERR("[Replay] Can't load state without replay data during recording.\n");
return false;
}
if (BSV_MOVIE_IS_PLAYBACK_ON() && !seen_replay && !frame_is_reversed)
{
/* TODO OSD message */
RARCH_WARN("[Replay] Loading state without replay data during replay will cancel replay.\n");
movie_stop(input_st);
}
}
#endif
if (!core_unserialize(&serial_info))
return false;
seen_core = true;
}
#ifdef HAVE_CHEEVOS
else if (memcmp(marker, RASTATE_CHEEVOS_BLOCK, 4) == 0)
{
if (rcheevos_set_serialized_data(input, block_size))
seen_cheevos = true;
}
#endif
#ifdef HAVE_BSV_MOVIE
else if (memcmp(marker, RASTATE_REPLAY_BLOCK, 4) == 0)
{
#ifdef HAVE_REWIND
bool frame_is_reversed = state_manager_frame_is_reversed();
#else
bool frame_is_reversed = false;
#endif
if (frame_is_reversed || replay_set_serialized_data(input_state_get_ptr(), (void*)input,
block_size))
seen_replay = true;
else
return false;
}
#endif
input += advance;
}
if (!seen_core)
{
RARCH_LOG("[State] No core.\n");
return false;
}
#ifdef HAVE_CHEEVOS
if (!seen_cheevos)
rcheevos_set_serialized_data(NULL, 0);
#endif
#ifdef HAVE_BSV_MOVIE
{
#ifdef HAVE_REWIND
bool frame_is_reversed = state_manager_frame_is_reversed();
#else
bool frame_is_reversed = false;
#endif
if (!seen_replay && !frame_is_reversed)
replay_set_serialized_data(input_state_get_ptr(), NULL, 0);
}
#endif
return true;
}
bool content_deserialize_state(const void *s, size_t len)
{
/* Both branches below read the first 8 bytes: the memcmp reads 7
* and the version switch reads input[7]. Neither was guarded, so
* a state file shorter than the identifier overread here before
* the block walker was ever reached. */
if (!s || len < 8)
return false;
if (memcmp(s, "RASTATE", 7) != 0)
{
/* old format is just core data, load it directly */
retro_ctx_serialize_info_t serial_info;
serial_info.data_const = s;
serial_info.size = len;
if (!core_unserialize(&serial_info))
return false;
#ifdef HAVE_CHEEVOS
rcheevos_set_serialized_data(NULL, 0);
#endif
#ifdef HAVE_BSV_MOVIE
{
#ifdef HAVE_REWIND
bool frame_is_reversed = state_manager_frame_is_reversed();
#else
bool frame_is_reversed = false;
#endif
if (!frame_is_reversed)
replay_set_serialized_data(input_state_get_ptr(), NULL, 0);
}
#endif
}
else
{
unsigned char* input = (unsigned char*)s;
switch (input[7]) /* version */
{
case 1:
if (content_load_rastate1(input, len))
break;
/* fall-through intentional */
default:
return false;
}
}
return true;
}
/**
* content_load_state_cb:
* @path : path that state will be loaded from.
* Load a state from disk to memory.
*
**/
static void content_load_state_apply(retro_task_t *task,
void *task_data,
void *user_data, const char *error)
{
unsigned i;
bool ret;
load_task_data_t *load_data = (load_task_data_t*)task_data;
ssize_t _len;
unsigned num_blocks = 0;
void *buf;
struct sram_block *blocks = NULL;
struct string_list *savefile_list = (struct string_list*)savefile_ptr_get();
/* The state is applied below; the load is no longer pending
* whichever way this callback exits. */
if (load_state_tasks_pending)
load_state_tasks_pending--;
_len = load_data->size;
buf = load_data->data;
#ifdef HAVE_CHEEVOS
if (rcheevos_hardcore_active())
goto error;
#endif
RARCH_LOG("[State] %s \"%s\", %u %s.\n",
msg_hash_to_str(MSG_LOADING_STATE),
load_data->path,
(unsigned)_len,
msg_hash_to_str(MSG_BYTES));
if (_len < 0 || !buf)
goto error;
/* This means we're backing up the file in memory,
* so content_undo_save_state()
* can restore it */
if (load_data->flags & SAVE_TASK_FLAG_LOAD_TO_BACKUP_BUFF)
{
/* If we were previously backing up a file, let go of it first */
if (undo_save_buf.data)
free(undo_save_buf.data);
undo_save_buf.data = buf;
undo_save_buf.size = _len;
undo_save_buf.capacity = (size_t)_len;
strlcpy(undo_save_buf.path, load_data->path, sizeof(undo_save_buf.path));
if (load_data->done_cb)
load_data->done_cb(task, load_data->path, user_data, NULL);
free(load_data);
return;
}
if ( savefile_list
&& savefile_list->size
&& config_get_ptr()->bools.block_sram_overwrite
)
{
RARCH_LOG("[SRAM] %s.\n",
msg_hash_to_str(MSG_BLOCKING_SRAM_OVERWRITE));
if ((blocks = (struct sram_block*)
calloc(savefile_list->size, sizeof(*blocks))))
{
num_blocks = (unsigned)savefile_list->size;
for (i = 0; i < num_blocks; i++)
blocks[i].type = savefile_list->elems[i].attr.i;
}
}
for (i = 0; i < num_blocks; i++)
{
retro_ctx_memory_info_t mem_info;
mem_info.id = blocks[i].type;
core_get_memory(&mem_info);
blocks[i].size = mem_info.size;
}
for (i = 0; i < num_blocks; i++)
if (blocks[i].size)
blocks[i].data = malloc(blocks[i].size);
/* Backup current SRAM which is overwritten by unserialize. */
for (i = 0; i < num_blocks; i++)
{
if (blocks[i].data)
{
retro_ctx_memory_info_t mem_info;
const void *ptr = NULL;
mem_info.id = blocks[i].type;
core_get_memory(&mem_info);
if ((ptr = mem_info.data))
memcpy(blocks[i].data, ptr, blocks[i].size);
}
}
/* A state load is a discontinuity in the game's own audio, and the work
* below blocks this thread long enough for the device to run dry. End the
* stream on the pause tail first and bring it back on the resume ramp, so
* both land in silence. See audio_driver_jump_fade_begin(). */
{
bool ramped = audio_driver_jump_fade_begin();
/* Backup the current state so we can undo this load */
content_save_state("RAM", false);
ret = content_deserialize_state(buf, _len);
audio_driver_jump_fade_end(ramped);
}
/* Flush back. */
for (i = 0; i < num_blocks; i++)
{
if (blocks[i].data)
{
retro_ctx_memory_info_t mem_info;
void *ptr = NULL;
mem_info.id = blocks[i].type;
core_get_memory(&mem_info);
if ((ptr = mem_info.data))
memcpy(ptr, blocks[i].data, blocks[i].size);
}
}
for (i = 0; i < num_blocks; i++)
free(blocks[i].data);
free(blocks);
if (!ret)
goto error;
if (load_data->done_cb)
load_data->done_cb(task, load_data->path, user_data, NULL);
free(buf);
free(load_data);
return;
error:
RARCH_ERR("[State] %s \"%s\".\n",
msg_hash_to_str(MSG_FAILED_TO_LOAD_STATE),
load_data->path);
if (load_data->done_cb)
{
char msg[PATH_MAX_LENGTH + 64];
snprintf(msg, sizeof(msg), "%s \"%s\".",
msg_hash_to_str(MSG_FAILED_TO_LOAD_STATE), load_data->path);
load_data->done_cb(task, load_data->path, user_data,
error ? error : msg);
}
if (buf)
free(buf);
free(load_data);
}
static void content_load_state_cb(retro_task_t *task,
void *task_data,
void *user_data, const char *error)
{
content_load_state_apply(task, task_data, user_data, error);
state_tasks_release();
}
/**
* save_state_cb:
*
* Called after the save state is done. Takes a screenshot if needed.
**/
static void save_state_cb(retro_task_t *task,
void *task_data,
void *user_data, const char *error)
{
save_task_state_t *state = (save_task_state_t*)task_data;
/* Out of the core whichever way this callback exits. */
if (save_state_tasks_pending)
save_state_tasks_pending--;
if (state->done_cb)
state->done_cb(task, state->path, user_data, error);
#ifdef HAVE_SCREENSHOTS
{
char *path = strdup(state->path);
if (state->flags & SAVE_TASK_FLAG_THUMBNAIL_ENABLE)
take_screenshot(config_get_ptr()->paths.directory_screenshot,
path, true,
state->flags & SAVE_TASK_FLAG_HAS_VALID_FB, false, true);
free(path);
}
#endif
free(state);
state_tasks_release();
}
#if defined(HAVE_BSV_MOVIE) || defined(HAVE_CHEEVOS)
/* The frontend's rastate blocks, captured where they belong: the main
* thread, when the background save is pushed. The worker then
* serializes only the core - which is what the core's
* SET_SAVE_STATE_IN_BACKGROUND request vouches for. */
static void content_capture_frontend_blocks(save_task_state_t *state)
{
#ifdef HAVE_BSV_MOVIE
{
input_driver_state_t *input_st = input_state_get_ptr();
#ifdef HAVE_REWIND
bool frame_is_reversed = state_manager_frame_is_reversed();
#else
bool frame_is_reversed = false;
#endif
if ( (input_st->bsv_movie_state.flags
& (BSV_FLAG_MOVIE_RECORDING | BSV_FLAG_MOVIE_PLAYBACK))
&& !frame_is_reversed)
{
size_t _len = replay_get_serialize_size(input_st);
if (_len > 0 && (state->fe_replay = malloc(_len)))
{
if (replay_get_serialized_data(input_st, state->fe_replay))
state->fe_replay_size = _len;
else
{
free(state->fe_replay);
state->fe_replay = NULL;
}
}
}
}
#endif
#ifdef HAVE_CHEEVOS
{
size_t _len = rcheevos_get_serialize_size();
if (_len > 0 && (state->fe_cheevos = malloc(_len)))
{
if (rcheevos_get_serialized_data(state->fe_cheevos))
state->fe_cheevos_size = _len;
else
{
free(state->fe_cheevos);
state->fe_cheevos = NULL;
}
}
}
#endif
}
#endif
/**
* task_push_save_state:
* @path : file path of the save state
* @data : the save state data to write
* @size : the total size of the save state
*
* Create a new task to save the content state.
**/
static bool task_push_save_state(const char *path, void *data, size_t len,
bool autosave, bool first, retro_task_callback_t cb, void *user_data)
{
settings_t *settings = config_get_ptr();
retro_task_t *task = task_init();
save_task_state_t *state = (save_task_state_t*)calloc(1, sizeof(*state));
if (!task || !state)
goto error;
strlcpy(state->path, path, sizeof(state->path));
state->data = data;
state->size = len;
#if defined(HAVE_BSV_MOVIE) || defined(HAVE_CHEEVOS)
/* A background save arrives without data: the worker serializes
* the core, and the frontend's blocks are captured here, on the
* main thread. */
if (!data)
content_capture_frontend_blocks(state);
#endif
/* Don't show OSD messages if we are auto-saving */
if (autosave)
state->flags |= ( SAVE_TASK_FLAG_AUTOSAVE
| SAVE_TASK_FLAG_MUTE);
if (settings->bools.savestate_thumbnail_enable)
{
/* Delay OSD messages and widgets for a few frames
* to prevent GPU screenshots from having notifications */
runloop_state_t *runloop_st = runloop_state_get_ptr();
runloop_st->msg_queue_delay = 12;
state->flags |= SAVE_TASK_FLAG_THUMBNAIL_ENABLE;
}
state->state_slot = settings->ints.state_slot;
if (video_driver_cached_frame_is_hw_render())
state->flags |= SAVE_TASK_FLAG_HAS_VALID_FB;
#if defined(HAVE_COMPRESSION)
if (settings->bools.savestate_file_compression)
state->flags |= SAVE_TASK_FLAG_COMPRESS_FILES;
#endif
if (!settings->bools.notification_show_save_state)
state->flags |= SAVE_TASK_FLAG_MUTE;
task->type = TASK_TYPE_BLOCKING;
task->state = state;
task->handler = task_save_handler;
task->callback = save_state_cb;
task->user_data = user_data;
state->done_cb = cb;
task->title = strdup(msg_hash_to_str(MSG_SAVING_STATE));
if (state->flags & SAVE_TASK_FLAG_MUTE)
task->flags |= RETRO_TASK_FLG_MUTE;
else
task->flags &= ~RETRO_TASK_FLG_MUTE;
if (first ? state_task_push_first(task) : state_task_push(task))
{
save_state_tasks_pending++;
return true;
}
else
{
/* The hold is full. */
if (data)
free(data);
if (task->title)
task_free_title(task);
free(task);
free(state);
}
return false;
error:
if (data)
free(data);
if (state)
free(state);
if (task)
{
if (task->title)
task_free_title(task);
free(task);
}
return false;
}
/**
* content_load_and_save_state_cb:
* @path : path that state will be loaded from.
* Load then save a state.
*
**/
static void content_load_and_save_state_cb(retro_task_t *task,
void *task_data,
void *user_data, const char *error)
{
load_task_data_t *load_data = (load_task_data_t*)task_data;
char *path;
void *data;
size_t size;
bool autosave;
retro_task_callback_t done_cb;
path = strdup(load_data->path);
data = load_data->undo_data;
size = load_data->undo_size;
autosave = (load_data->flags & SAVE_TASK_FLAG_AUTOSAVE) ? true : false;
/* the caller asked about the save, not this backup load */
done_cb = load_data->done_cb;
load_data->done_cb = NULL;
content_load_state_apply(task, task_data, user_data, error);
/* The save this backup load was for goes ahead of anything held: a
* Load State held behind it loads what it writes. */
if ( !task_push_save_state(path, data, size, autosave, true,
done_cb, user_data)
&& done_cb)
{
char msg[PATH_MAX_LENGTH + 64];
snprintf(msg, sizeof(msg), "%s \"%s\".",
msg_hash_to_str(MSG_FAILED_TO_SAVE_STATE_TO), path);
done_cb(NULL, path, user_data, msg);
}
free(path);
state_tasks_release();
}
/**
* task_push_load_and_save_state:
* @path : file path of the save state
* @data : the save state data to write
* @size : the total size of the save state
* @load_to_backup_buffer : If true, the state will be loaded into undo_save_buf.
*
* Create a new task to load current state first into a backup buffer (for undo)
* and then save the content state.
**/
static bool task_push_load_and_save_state(const char *path, void *data,
size_t len, bool load_to_backup_buffer, bool autosave,
retro_task_callback_t cb, void *user_data)
{
retro_task_t *task = NULL;
settings_t *settings = config_get_ptr();
save_task_state_t *state = (save_task_state_t*)
calloc(1, sizeof(*state));
if (!state)
return false;
if (!(task = task_init()))
{
free(state);
return false;
}
strlcpy(state->path, path, sizeof(state->path));
if (load_to_backup_buffer)
state->flags |= SAVE_TASK_FLAG_LOAD_TO_BACKUP_BUFF;
state->undo_size = len;
state->undo_data = data;
/* Don't show OSD messages if we are auto-saving */
if (autosave)
state->flags |= ( SAVE_TASK_FLAG_AUTOSAVE
| SAVE_TASK_FLAG_MUTE);
if (load_to_backup_buffer)
state->flags |= SAVE_TASK_FLAG_MUTE;
state->state_slot = settings->ints.state_slot;
if (video_driver_cached_frame_is_hw_render())
state->flags |= SAVE_TASK_FLAG_HAS_VALID_FB;
#if defined(HAVE_COMPRESSION)
if (settings->bools.savestate_file_compression)
state->flags |= SAVE_TASK_FLAG_COMPRESS_FILES;
#endif
if (!settings->bools.notification_show_save_state)
state->flags |= SAVE_TASK_FLAG_MUTE;
state->frame_count = &video_state_get_ptr()->frame_count;
task->state = state;
task->type = TASK_TYPE_BLOCKING;
task->handler = task_load_handler;
task->callback = content_load_and_save_state_cb;
task->user_data = user_data;
state->done_cb = cb;
task->title = strdup(msg_hash_to_str(MSG_LOADING_STATE));
if (state->flags & SAVE_TASK_FLAG_MUTE)
task->flags |= RETRO_TASK_FLG_MUTE;
else
task->flags &= ~RETRO_TASK_FLG_MUTE;
if (state_task_push(task))
load_state_tasks_pending++;
else
{
/* The hold is full. No callback will run for this task. */
if (data)
free(data);
if (task->title)
task_free_title(task);
free(task);
free(state);
return false;
}
return true;
}
/**
* content_auto_save_state:
* @path : path of saved state that shall be written to.
* Save a state from memory to disk. This is used for automatic saving right
* before a core unload/deinit or content closing. The save is a blocking
* operation (does not use the task queue).
*
* Returns: true if successful, false otherwise.
**/
bool content_auto_save_state(const char *path)
{
size_t _len;
int _close_ret;
settings_t *settings = config_get_ptr();
void *serial_data = NULL;
intfstream_t *file = NULL;
char tmp_path[PATH_MAX_LENGTH];
if (!core_info_current_supports_savestate())
{
RARCH_LOG("[State] %s\n",
msg_hash_to_str(MSG_CORE_DOES_NOT_SUPPORT_SAVESTATES));
return false;
}
_len = core_serialize_size();
if (_len == 0)
return false;
serial_data = content_get_serialized_data(&_len);
if (!serial_data)
return false;
/* Write to "<path>.tmp" and move it into place, so a crash or power
* loss mid-save leaves the previous state intact. */
if (!filestream_atomic_temp_name(tmp_path, sizeof(tmp_path), path))
{
free(serial_data);
return false;
}
#if defined(HAVE_COMPRESSION)
if (settings->bools.savestate_file_compression)
file = intfstream_open_rzip_file(tmp_path, RETRO_VFS_FILE_ACCESS_WRITE);
else
#endif
file = intfstream_open_file(tmp_path, RETRO_VFS_FILE_ACCESS_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_SEQUENTIAL_BULK);
if (!file)
{
free(serial_data);
return false;
}
if (_len != (size_t)intfstream_write(file, serial_data, _len))
{
intfstream_close(file);
free(serial_data);
free(file);
filestream_delete(tmp_path);
return false;
}
/* A failed close (rzip's last chunk, a full disk) is a failed save. */
_close_ret = intfstream_close(file);
free(serial_data);
free(file);
if (!content_finish_tmp(path, _close_ret == 0))
return false;
#ifdef HAVE_SCREENSHOTS
if (settings->bools.savestate_thumbnail_enable)
{
const char *dir_screenshot = settings->paths.directory_screenshot;
bool validfb = video_driver_cached_frame_is_hw_render();
take_screenshot(dir_screenshot, path, true, validfb, false, false);
}
#endif
return true;
}
/**
* content_save_state:
* @path : path of saved state that shall be written to.
* @save_to_disk: If false, saves the state onto undo_load_buf.
* @autosave: If the save is triggered automatically (ie. at core unload).
* Save a state from memory to disk.
*
* Returns: true if successful, false otherwise.
**/
bool content_save_state(const char *path, bool save_to_disk)
{
return content_save_state_notify(path, save_to_disk, NULL, NULL);
}
bool content_save_state_notify(const char *path, bool save_to_disk,
retro_task_callback_t cb, void *user_data)
{
size_t _len;
void *data = NULL;
if (!save_to_disk && save_state_disable_undo)
return false;
if (!core_info_current_supports_savestate())
{
RARCH_LOG("[State] %s\n",
msg_hash_to_str(MSG_CORE_DOES_NOT_SUPPORT_SAVESTATES));
return false;
}
_len = core_serialize_size();
if (_len == 0)
return false;
/* The undo snapshot is retaken on every state load, so this is the
* hottest serialize the frontend does, and it is the one where the
* destination can be kept between calls: nothing outside this
* function owns undo_load_buf's allocation. Split out ahead of
* the disk path, which cannot reuse anything - it hands its buffer
* to a task that takes ownership of it. */
if (!save_to_disk)
{
if (!(_len = content_serialize_reusing(&undo_load_spare.data,
&undo_load_spare.capacity)))
{
RARCH_ERR("[State] %s \"%s\".\n",
msg_hash_to_str(MSG_FAILED_TO_SAVE_STATE_TO),
path);
return false;
}
RARCH_LOG("[State] %s \"%s\", %u %s.\n",
msg_hash_to_str(MSG_SAVING_STATE),
path,
(unsigned)_len,
msg_hash_to_str(MSG_BYTES));
/* Swap rather than free-and-assign. The outgoing snapshot's
* allocation becomes the next spare - still mapped, still
* faulted in - and the freshly filled spare becomes the
* snapshot. Nothing is freed and nothing is copied.
*
* Swapping is also what keeps the old failure semantics exact:
* the serialize above wrote into the spare, not into the live
* snapshot, so a serialize that failed left the previous
* snapshot intact and undoable, which is what the
* fresh-allocation form did. */
{
void *outgoing = undo_load_buf.data;
size_t outgoing_cap = undo_load_buf.capacity;
undo_load_buf.data = undo_load_spare.data;
undo_load_buf.capacity = undo_load_spare.capacity;
undo_load_spare.data = outgoing;
undo_load_spare.capacity = outgoing_cap;
}
undo_load_buf.size = _len;
strlcpy(undo_load_buf.path, path, sizeof(undo_load_buf.path));
return true;
}
if (!save_state_in_background)
{
if (!(data = content_get_serialized_data(&_len)))
{
RARCH_ERR("[State] %s \"%s\".\n",
msg_hash_to_str(MSG_FAILED_TO_SAVE_STATE_TO),
path);
return false;
}
RARCH_LOG("[State] %s \"%s\", %u %s.\n",
msg_hash_to_str(MSG_SAVING_STATE),
path,
(unsigned)_len,
msg_hash_to_str(MSG_BYTES));
}
if (!save_state_disable_undo && path_is_valid(path))
{
/* Before overwriting the savestate file, load it into a buffer
to allow undo_save_state() to work */
/* TODO/FIXME - Use msg_hash_to_str here */
RARCH_LOG("[State] %s...\n",
msg_hash_to_str(MSG_FILE_ALREADY_EXISTS_SAVING_TO_BACKUP_BUFFER));
return task_push_load_and_save_state(path, data, _len, true, false,
cb, user_data);
}
return task_push_save_state(path, data, _len, false, false, cb, user_data);
}
/**
* content_ram_state_pending:
* Check a RAM state write to disk.
*
* @return true if need to write, false otherwise.
**/
bool content_ram_state_pending(void)
{
return ram_buf.to_write_file;
}
/* True from the moment a save state task is pushed until its
* main-thread callback has run: the task is inside the core until
* then. A flag, not a finder, for the reason given at
* content_load_state_in_progress() below - the close asks from
* inside a task handler, where a finder cannot see the save. */
bool content_save_state_in_progress(void* data)
{
(void)data;
return save_state_tasks_pending != 0;
}
/* Blocks until the state task is through. For the exit, startup and
* init-failure paths only; CI keeps it out of menu/ and tasks/. */
void content_wait_for_save_state_task(void)
{
task_queue_wait(content_save_state_in_progress, NULL);
}
/* Returns true if a load state task is in progress.
*
* Declared with the other file statics above.
*
* True from the moment a load task is pushed until its main-thread
* callback has applied the state.
*
* Deliberately NOT derived from task_queue_find(). The unthreaded
* gather lifts EVERY running task off the queue into a local list
* before invoking any handler, so for the whole of that pass the
* queue looks empty to a finder - including to a finder called from
* inside another task's handler, which is exactly how this is used.
* A sibling load task is invisible there, so a finder answers "no
* load in progress" while the load is sitting a few entries away in
* the same pass, waiting its turn. The threaded gather has a
* narrower version of the same window between a worker finishing and
* its callback running.
*
* The flag transitions strictly on the main thread (push, then
* callback), so neither window exists. */
bool content_load_state_in_progress(void* data)
{
return load_state_tasks_pending != 0;
}
/* As content_wait_for_save_state_task(), for a load. */
void content_wait_for_load_state_task(void)
{
task_queue_wait(content_load_state_in_progress, NULL);
}
/**
* content_load_state:
* @path : path that state will be loaded from.
* @load_to_backup_buffer : If true, state will be loaded into undo_save_buf.
* Load a state from disk to memory.
*
* @return true if successful, false otherwise.
**/
bool content_load_state(const char *path,
bool load_to_backup_buffer, bool autoload)
{
return content_load_state_notify(path, load_to_backup_buffer, autoload,
NULL, NULL);
}
bool content_load_state_notify(const char *path,
bool load_to_backup_buffer, bool autoload,
retro_task_callback_t cb, void *user_data)
{
retro_task_t *task = NULL;
save_task_state_t *state = NULL;
settings_t *settings = config_get_ptr();
/* Loading is gated by the artifact, not by save-capability: a state
* file the user produced outranks (possibly stale) core metadata, and
* a core may be able to restore in situations where it cannot
* currently serialize - e.g. from a game's own main menu, where
* retro_serialize_size() is legitimately 0 but retro_unserialize()
* performs a full restore. retro_unserialize() is the final arbiter
* and fails gracefully. */
if ( !core_info_current_supports_savestate()
&& !load_to_backup_buffer
&& !path_is_valid(path))
{
RARCH_LOG("[State] %s\n",
msg_hash_to_str(MSG_CORE_DOES_NOT_SUPPORT_SAVESTATES));
goto error;
}
task = task_init();
state = (save_task_state_t*)calloc(1, sizeof(*state));
if (!task || !state)
goto error;
strlcpy(state->path, path, sizeof(state->path));
if (load_to_backup_buffer)
state->flags |= SAVE_TASK_FLAG_LOAD_TO_BACKUP_BUFF;
if (autoload)
state->flags |= SAVE_TASK_FLAG_AUTOLOAD;
state->state_slot = settings->ints.state_slot;
if (video_driver_cached_frame_is_hw_render())
state->flags |= SAVE_TASK_FLAG_HAS_VALID_FB;
#if defined(HAVE_COMPRESSION)
if (settings->bools.savestate_file_compression)
state->flags |= SAVE_TASK_FLAG_COMPRESS_FILES;
#endif
if (!settings->bools.notification_show_save_state)
state->flags |= SAVE_TASK_FLAG_MUTE;
state->frame_count = &video_state_get_ptr()->frame_count;
task->type = TASK_TYPE_BLOCKING;
task->state = state;
task->handler = task_load_handler;
task->callback = content_load_state_cb;
task->user_data = user_data;
state->done_cb = cb;
task->title = strdup(msg_hash_to_str(MSG_LOADING_STATE));
if (state->flags & SAVE_TASK_FLAG_MUTE)
task->flags |= RETRO_TASK_FLG_MUTE;
else
task->flags &= ~RETRO_TASK_FLG_MUTE;
/* Held behind a state task in flight rather than dropped: Load State
* pressed while a save was still writing did nothing. */
if (state_task_push(task))
load_state_tasks_pending++;
else
{
/* The hold is full. No callback will run for this task, so the
* load is not pending - counted, it would hold a content close
* and a movie recording waiting on a load that never comes - and
* nothing else frees what it holds. */
task_free_title(task);
free(task);
free(state);
return false;
}
return true;
error:
if (state)
free(state);
if (task)
free(task);
return false;
}
bool content_rename_state(const char *origin, const char *dest)
{
if (filestream_exists(dest))
filestream_delete(dest);
if (!filestream_rename(origin, dest))
return true;
RARCH_ERR("[State] Error renaming file \"%s\".\n", origin);
return false;
}
/*
*
* TODO/FIXME: Figure out when and where this should be called.
* As it is, when e.g. closing Gambatte, we get the
* same printf message 4 times.
*/
/* Anything still held when the content goes: the waits ahead of a
* close run them, so this is only what a reset met without one. The
* caller of each is told it did not happen. */
static void state_tasks_discard_held(void)
{
while (state_tasks_held_count)
{
retro_task_t *task = state_tasks_held[--state_tasks_held_count];
save_task_state_t *state = (save_task_state_t*)task->state;
bool is_load = task->callback == content_load_state_cb
|| task->callback == content_load_and_save_state_cb;
state_tasks_held[state_tasks_held_count] = NULL;
if (is_load)
{
if (load_state_tasks_pending)
load_state_tasks_pending--;
}
else if (save_state_tasks_pending)
save_state_tasks_pending--;
if (state)
{
if (state->done_cb)
state->done_cb(NULL, state->path, task->user_data,
msg_hash_to_str(is_load
? MSG_FAILED_TO_LOAD_STATE
: MSG_FAILED_TO_SAVE_STATE_TO));
/* an undo writes undo_save_buf's own allocation */
if ( state->data
&& !(state->flags & SAVE_TASK_FLAG_UNDO_SAVE))
free(state->data);
free(state->undo_data);
free(state->fe_replay);
free(state->fe_cheevos);
free(state);
}
task_free_title(task);
free(task);
}
}
void content_reset_savestate_backups(void)
{
state_tasks_discard_held();
if (undo_save_buf.data)
{
free(undo_save_buf.data);
undo_save_buf.data = NULL;
}
undo_save_buf.path[0] = '\0';
undo_save_buf.size = 0;
undo_save_buf.capacity = 0;
if (undo_load_buf.data)
{
free(undo_load_buf.data);
undo_load_buf.data = NULL;
}
undo_load_buf.path[0] = '\0';
undo_load_buf.size = 0;
undo_load_buf.capacity = 0;
/* The spare is an allocation nobody else knows about, so this is
* the only place it can be released. Resetting the backups is
* exactly the point at which holding a state-sized buffer for a
* snapshot that no longer exists stops being worth it. */
if (undo_load_spare.data)
{
free(undo_load_spare.data);
undo_load_spare.data = NULL;
}
undo_load_spare.capacity = 0;
if (ram_buf.state_buf.data)
{
free(ram_buf.state_buf.data);
ram_buf.state_buf.data = NULL;
ram_buf.state_buf.capacity = 0;
}
ram_buf.state_buf.path[0] = '\0';
ram_buf.state_buf.size = 0;
ram_buf.state_buf.capacity = 0;
ram_buf.to_write_file = false;
}
bool content_undo_load_buf_is_empty(void)
{
return undo_load_buf.data == NULL || undo_load_buf.size == 0;
}
bool content_undo_save_buf_is_empty(void)
{
return undo_save_buf.data == NULL || undo_save_buf.size == 0;
}
bool content_undo_save_disabled(void)
{
return save_state_disable_undo;
}
/**
* content_load_state_from_ram:
* Load a state from RAM.
*
* @return true if successful, false otherwise.
**/
bool content_load_state_from_ram(void)
{
bool ret = false;
bool ramped = false;
if (!core_info_current_supports_savestate())
{
RARCH_LOG("[State] %s\n",
msg_hash_to_str(MSG_CORE_DOES_NOT_SUPPORT_SAVESTATES));
return false;
}
if (!ram_buf.state_buf.data)
return false;
RARCH_LOG("[State] %s, %u %s.\n",
msg_hash_to_str(MSG_LOADING_STATE),
(unsigned)ram_buf.state_buf.size,
msg_hash_to_str(MSG_BYTES));
/* Same discontinuity as a load from disk, so the same bracket. See
* audio_driver_jump_fade_begin(). */
ramped = audio_driver_jump_fade_begin();
/* Backup the current state so we can undo this load */
content_save_state("RAM", false);
ret = content_deserialize_state(ram_buf.state_buf.data, ram_buf.state_buf.size);
audio_driver_jump_fade_end(ramped);
if (!ret)
{
RARCH_ERR("[State] %s.\n",
msg_hash_to_str(MSG_FAILED_TO_LOAD_SRAM));
return false;
}
return true;
}
/**
* content_save_state_from_ram:
* Save a state to RAM.
*
* @return true if successful, false otherwise.
**/
bool content_save_state_to_ram(void)
{
size_t _len;
void *data = NULL;
if (!core_info_current_supports_savestate())
{
RARCH_LOG("[State] %s\n",
msg_hash_to_str(MSG_CORE_DOES_NOT_SUPPORT_SAVESTATES));
return false;
}
_len = core_serialize_size();
if (_len == 0)
return false;
RARCH_LOG("[State] %s, %u %s.\n",
msg_hash_to_str(MSG_SAVING_STATE),
(unsigned)_len,
msg_hash_to_str(MSG_BYTES));
if (save_state_disable_undo && ram_buf.state_buf.data)
{
/* Undo off means lack of memory, free before we alloc the new one */
free(ram_buf.state_buf.data);
ram_buf.state_buf.data = NULL;
ram_buf.state_buf.capacity = 0;
}
if (!(data = content_get_serialized_data(&_len)))
{
RARCH_ERR("[State] %s.\n",
msg_hash_to_str(MSG_FAILED_TO_SAVE_SRAM));
return false;
}
if (ram_buf.state_buf.data)
free(ram_buf.state_buf.data);
ram_buf.state_buf.data = data;
ram_buf.state_buf.size = _len;
ram_buf.state_buf.capacity = _len;
ram_buf.to_write_file = true;
return true;
}
/**
* content_ram_state_to_file:
* @path : path of RAM state that shall be written to.
*
* Save a RAM state from memory to disk.
*
* @return true if successful, false otherwise.
**/
bool content_ram_state_to_file(const char *path)
{
if ( path
&& ram_buf.state_buf.data
&& ram_buf.to_write_file)
{
/* Write via a temporary file, so a crash or power loss mid-save
* leaves the previous file intact instead of a truncated one. */
bool written = false;
#if defined(HAVE_COMPRESSION)
settings_t *settings = config_get_ptr();
if (settings->bools.save_file_compression)
{
char tmp_path[PATH_MAX_LENGTH];
if (filestream_atomic_temp_name(tmp_path, sizeof(tmp_path), path))
written = content_finish_tmp(path, rzipstream_write_file(tmp_path,
ram_buf.state_buf.data, ram_buf.state_buf.size));
}
else
#endif
written = filestream_write_file_atomic(path,
ram_buf.state_buf.data, ram_buf.state_buf.size);
if (written)
goto success;
}
return false;
success:
ram_buf.to_write_file = false;
return true;
}
void set_save_state_in_background(bool state)
{
save_state_in_background = state;
}
void set_save_state_disable_undo(bool disable)
{
save_state_disable_undo = disable;
}
bool content_save_state_automatic(retro_time_t now_us)
{
char savestate_path[PATH_MAX_LENGTH];
settings_t *settings = config_get_ptr();
unsigned savestate_automatic_interval =
settings->uints.savestate_automatic_interval;
/* Return early if automatic savestate is disabled,
safety checks already happen in content_auto_save_state() */
if (savestate_automatic_interval == 0)
return false;
/* Check how long since last autosavestate, against the clock the
* frame already read - no time() call of its own per frame. */
if ((now_us - last_savestate_automatic_time)
< (retro_time_t)savestate_automatic_interval * 1000000)
return false;
/* Generate the savestate path */
if (!runloop_get_savestate_path(savestate_path,
sizeof(savestate_path), -1))
{
RARCH_WARN("[State] %s\n",
msg_hash_to_str(MSG_FAILED_TO_SAVE_STATE_TO));
return false;
}
/* Trigger the savestate */
RARCH_LOG("[State] %s (automatic) to \"%s\".\n",
msg_hash_to_str(MSG_SAVING_STATE),
savestate_path);
/* Update the last savestate time, rinse/repeat */
last_savestate_automatic_time = now_us;
return content_auto_save_state(savestate_path);
}