mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-13 04:51:38 -10:00
275 lines
8.6 KiB
C
275 lines
8.6 KiB
C
/* Cloud sync on content unload, in the shipping frontend.
|
|
*
|
|
* With Cloud Sync in automatic mode, closing content asks for a sync
|
|
* so the save the content leaves behind reaches the server. The save
|
|
* RAM only reaches disk when the content's deinit flushes it, which a
|
|
* close from the menu runs a frame or more later, and until that
|
|
* deinit finishes the save file belongs to the running core (the sync
|
|
* defers anything it would write there). A sync started before then
|
|
* uploads, and hashes, whatever the file held last.
|
|
*
|
|
* This links the real retroarch.c and loads sram_core.so, writes a
|
|
* pattern into the core's save RAM and closes the content through
|
|
* CMD_EVENT_UNLOAD_CORE, running frames until the close completes.
|
|
* task_push_cloud_sync is wrapped (-Wl,--wrap): at each call the
|
|
* harness records what the save file holds and whether a core still
|
|
* owns it. No server is contacted.
|
|
*
|
|
* Lanes:
|
|
* startup content from the command line: the startup sync runs to
|
|
* its end before the core takes the save file.
|
|
* unload one sync, pushed once the file on disk is the core's
|
|
* last save RAM and no core owns it.
|
|
* no content unloading with no content running syncs at once, as
|
|
* before: nothing waits to be flushed.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <dlfcn.h>
|
|
|
|
#include <boolean.h>
|
|
|
|
#include "../../../runloop.h"
|
|
#include "../../../retroarch.h"
|
|
#include "../../../command.h"
|
|
#include "../../../content.h"
|
|
#include "../../../configuration.h"
|
|
#include "../../../frontend/frontend_driver.h"
|
|
#include "../../../tasks/tasks_internal.h"
|
|
#include "../../../verbosity.h"
|
|
|
|
#include <time/rtime.h>
|
|
#include <file/config_file.h>
|
|
#include <queues/task_queue.h>
|
|
|
|
static unsigned failures = 0;
|
|
|
|
#define CHECK(cond, ...) \
|
|
do { \
|
|
if (!(cond)) \
|
|
{ \
|
|
fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \
|
|
fprintf(stderr, __VA_ARGS__); \
|
|
fprintf(stderr, "\n"); \
|
|
failures++; \
|
|
} \
|
|
} while (0)
|
|
|
|
/* ---- the wrapped push ---------------------------------------------- */
|
|
|
|
static char srm_path[600];
|
|
static uint8_t pattern[256];
|
|
static size_t pattern_size;
|
|
static unsigned n_syncs;
|
|
static bool last_on_disk;
|
|
static bool last_live;
|
|
|
|
static bool file_holds_pattern(void)
|
|
{
|
|
uint8_t buf[512];
|
|
size_t n;
|
|
FILE *f = fopen(srm_path, "rb");
|
|
if (!f)
|
|
return false;
|
|
n = fread(buf, 1, sizeof(buf), f);
|
|
fclose(f);
|
|
return n == pattern_size && !memcmp(buf, pattern, pattern_size);
|
|
}
|
|
|
|
bool __wrap_task_push_cloud_sync(retro_task_callback_t cb, void *cb_data)
|
|
{
|
|
n_syncs++;
|
|
last_on_disk = file_holds_pattern();
|
|
last_live = content_savefile_is_live(srm_path);
|
|
/* Done at once: a caller that waits for it goes on. */
|
|
if (cb)
|
|
cb(NULL, NULL, cb_data, NULL);
|
|
return true;
|
|
}
|
|
|
|
/* ---- helpers ------------------------------------------------------- */
|
|
|
|
static bool content_running(void)
|
|
{
|
|
return (content_get_flags() & CONTENT_ST_FLAG_IS_INITED)
|
|
&& runloop_state_get_ptr()->current_core_type != CORE_TYPE_DUMMY;
|
|
}
|
|
|
|
static void run_until_closed(void)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < 600; i++)
|
|
{
|
|
if (!content_running() && !runloop_state_get_ptr()->content_switching)
|
|
return;
|
|
/* One pass of the frontend's main loop. */
|
|
runloop_iterate();
|
|
task_queue_check();
|
|
}
|
|
}
|
|
|
|
/* ---- lanes --------------------------------------------------------- */
|
|
|
|
static void lane_unload(const char *core_path)
|
|
{
|
|
void *core = dlopen(core_path, RTLD_NOW | RTLD_NOLOAD);
|
|
uint8_t *(*get_sram)(void);
|
|
uint8_t *sram;
|
|
size_t i;
|
|
|
|
CHECK(content_running(), "the content did not load");
|
|
CHECK(core != NULL, "sram_core.so is not loaded");
|
|
if (!core || !content_running())
|
|
return;
|
|
get_sram = (uint8_t *(*)(void))dlsym(core, "harness_core_sram");
|
|
CHECK(get_sram != NULL, "sram_core.so has no harness_core_sram");
|
|
if (!get_sram)
|
|
return;
|
|
sram = get_sram();
|
|
pattern_size = 256;
|
|
for (i = 0; i < pattern_size; i++)
|
|
pattern[i] = (uint8_t)(0xA5 ^ i);
|
|
memcpy(sram, pattern, pattern_size);
|
|
dlclose(core);
|
|
|
|
CHECK(content_savefile_is_live(srm_path),
|
|
"the save file is not the running core's: %s", srm_path);
|
|
|
|
n_syncs = 0;
|
|
command_event(CMD_EVENT_UNLOAD_CORE, NULL);
|
|
run_until_closed();
|
|
|
|
CHECK(!content_running(), "the content did not close");
|
|
CHECK(n_syncs == 1, "%u syncs on unload, expected 1", n_syncs);
|
|
CHECK(last_on_disk,
|
|
"the sync was pushed before the save RAM reached disk");
|
|
CHECK(!last_live,
|
|
"the sync was pushed while a core still owned the save file");
|
|
printf("[%s] unload\n", failures ? "FAIL" : "ok");
|
|
}
|
|
|
|
static void lane_startup(void)
|
|
{
|
|
unsigned before = failures;
|
|
CHECK(n_syncs == 1, "%u syncs before the content loaded, expected 1", n_syncs);
|
|
CHECK(!last_live, "the startup sync was pushed after the core took the save file");
|
|
CHECK(content_running(), "the content did not load after the startup sync");
|
|
printf("[%s] startup\n", failures == before ? "ok" : "FAIL");
|
|
}
|
|
|
|
static void lane_no_content(void)
|
|
{
|
|
unsigned before = failures;
|
|
n_syncs = 0;
|
|
command_event(CMD_EVENT_UNLOAD_CORE, NULL);
|
|
CHECK(n_syncs == 1, "%u syncs on an unload with no content, expected 1 at once",
|
|
n_syncs);
|
|
run_until_closed();
|
|
CHECK(n_syncs == 1, "%u syncs after the frames that follow, expected 1",
|
|
n_syncs);
|
|
printf("[%s] no content\n", failures == before ? "ok" : "FAIL");
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
char cfg_path[512];
|
|
char content_path[512];
|
|
char core_path[1024];
|
|
char cmd[700];
|
|
char dir[400];
|
|
char *rarch_argv[8];
|
|
int rarch_argc = 0;
|
|
FILE *f;
|
|
const char *self = argc > 0 ? argv[0] : "cloudsync_unload_test";
|
|
const char *slash;
|
|
|
|
/* sram_core.so sits next to the harness. */
|
|
{
|
|
char cwd[400];
|
|
int dir_len = (slash = strrchr(self, '/')) ? (int)(slash - self) : 1;
|
|
const char *self_dir = slash ? self : ".";
|
|
if (self_dir[0] == '/')
|
|
snprintf(core_path, sizeof(core_path), "%.*s/sram_core.so",
|
|
dir_len, self_dir);
|
|
else if (getcwd(cwd, sizeof(cwd)))
|
|
snprintf(core_path, sizeof(core_path), "%s/%.*s/sram_core.so",
|
|
cwd, dir_len, self_dir);
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
snprintf(dir, sizeof(dir), "/tmp/cloudsync_unload_%ld", (long)getpid());
|
|
snprintf(cmd, sizeof(cmd), "mkdir -p %s", dir);
|
|
if (system(cmd) != 0)
|
|
return 1;
|
|
|
|
snprintf(content_path, sizeof(content_path), "%s/game.bin", dir);
|
|
snprintf(srm_path, sizeof(srm_path), "%s/game.srm", dir);
|
|
if ((f = fopen(content_path, "wb")))
|
|
{
|
|
fputs("game", f);
|
|
fclose(f);
|
|
}
|
|
|
|
snprintf(cfg_path, sizeof(cfg_path), "%s/harness.cfg", dir);
|
|
if ((f = fopen(cfg_path, "wb")))
|
|
{
|
|
fprintf(f, "video_driver = \"null\"\n");
|
|
fprintf(f, "audio_driver = \"null\"\n");
|
|
fprintf(f, "input_driver = \"null\"\n");
|
|
fprintf(f, "input_joypad_driver = \"null\"\n");
|
|
fprintf(f, "menu_driver = \"rgui\"\n");
|
|
fprintf(f, "video_threaded = \"false\"\n");
|
|
fprintf(f, "config_save_on_exit = \"false\"\n");
|
|
fprintf(f, "savefile_directory = \"%s\"\n", dir);
|
|
fprintf(f, "savestate_directory = \"%s\"\n", dir);
|
|
fprintf(f, "sort_savefiles_enable = \"false\"\n");
|
|
fprintf(f, "sort_savefiles_by_content_enable = \"false\"\n");
|
|
fprintf(f, "savestate_auto_save = \"false\"\n");
|
|
fprintf(f, "save_file_compression = \"false\"\n");
|
|
fprintf(f, "autosave_interval = \"0\"\n");
|
|
fprintf(f, "cloud_sync_enable = \"true\"\n");
|
|
fprintf(f, "cloud_sync_sync_mode = \"0\"\n");
|
|
fclose(f);
|
|
}
|
|
|
|
config_file_set_io_default(config_file_io_filestream());
|
|
rtime_init();
|
|
retroarch_config_init();
|
|
retroarch_ctl(RARCH_CTL_STATE_FREE, NULL);
|
|
frontend_driver_init_first(NULL);
|
|
|
|
rarch_argv[rarch_argc++] = (char*)"retroarch";
|
|
rarch_argv[rarch_argc++] = (char*)"--config";
|
|
rarch_argv[rarch_argc++] = cfg_path;
|
|
rarch_argv[rarch_argc++] = (char*)"-L";
|
|
rarch_argv[rarch_argc++] = core_path;
|
|
rarch_argv[rarch_argc++] = content_path;
|
|
if (getenv("HARNESS_VERBOSE"))
|
|
rarch_argv[rarch_argc++] = (char*)"-v";
|
|
|
|
if (!retroarch_main_init(rarch_argc, rarch_argv))
|
|
{
|
|
fprintf(stderr, "FAIL: retroarch_main_init failed\n");
|
|
return 1;
|
|
}
|
|
|
|
lane_startup();
|
|
lane_unload(core_path);
|
|
lane_no_content();
|
|
|
|
snprintf(cmd, sizeof(cmd), "rm -rf %s", dir);
|
|
if (system(cmd) != 0) { }
|
|
|
|
if (failures)
|
|
{
|
|
fprintf(stderr, "FAIL cloudsync_unload_test: %u failures\n", failures);
|
|
return 1;
|
|
}
|
|
printf("cloudsync_unload_test: all lanes pass\n");
|
|
return 0;
|
|
}
|