diff --git a/configuration.c b/configuration.c index 42684073a4..5a3e287d62 100644 --- a/configuration.c +++ b/configuration.c @@ -11178,6 +11178,31 @@ void config_save_file_salamander(void) if (conf) config_file_free(conf); } + +/* This frontend came up: the core salamander launched is not to be + * passed over at the next boot. */ +void config_clear_salamander_pending(void) +{ + config_file_t *conf = NULL; + char config_path[PATH_MAX_LENGTH]; + char pending[PATH_MAX_LENGTH]; + + if (!config_file_salamander_get_path(config_path, sizeof(config_path))) + return; + if (!(conf = config_file_new_from_path_to_string(config_path))) + return; + + if (config_get_path(conf, SALAMANDER_CONFIG_PENDING_KEY, + pending, sizeof(pending))) + { + config_unset(conf, SALAMANDER_CONFIG_PENDING_KEY); + if (!config_file_write(conf, config_path, false)) + RARCH_ERR("[Config] Failed to update salamander config file in: \"%s\".\n", + config_path); + } + + config_file_free(conf); +} #endif bool input_config_bind_map_get_valid(unsigned bind_index) diff --git a/configuration.h b/configuration.h index f827d11549..6cc596dc15 100644 --- a/configuration.h +++ b/configuration.h @@ -1715,6 +1715,7 @@ void config_load(void); * when launching a salamander build */ void config_load_file_salamander(void); void config_save_file_salamander(void); +void config_clear_salamander_pending(void); #endif void retroarch_config_init(void); diff --git a/file_path_special.h b/file_path_special.h index c3a34a6ef3..09c232c7df 100644 --- a/file_path_special.h +++ b/file_path_special.h @@ -72,6 +72,10 @@ RETRO_BEGIN_DECLS #define FILE_PATH_MAIN_CONFIG "retroarch.cfg" #define FILE_PATH_SALAMANDER_CONFIG "retroarch-salamander.cfg" #endif +/* Salamander config key: the core salamander last launched, until that + * core's frontend has started. Still set at the next boot, it names a + * core that never came up. */ +#define SALAMANDER_CONFIG_PENDING_KEY "libretro_path_pending" #define FILE_PATH_BACKGROUND_IMAGE "bg.png" #define FILE_PATH_TTF_FONT "font.ttf" #define FILE_PATH_RUNTIME_EXTENSION ".lrtl" diff --git a/frontend/frontend_salamander.c b/frontend/frontend_salamander.c index 3a5f3139ec..cac58efcd8 100644 --- a/frontend/frontend_salamander.c +++ b/frontend/frontend_salamander.c @@ -42,7 +42,7 @@ struct defaults g_defaults; */ static void find_first_libretro_core(char *first_file, size_t size_of_first_file, const char *dir, - const char * ext) + const char * ext, const char *skip) { size_t i; bool ret = false; @@ -71,13 +71,18 @@ static void find_first_libretro_core(char *first_file, fill_pathname_base(fname, libretro_elem, sizeof(fname)); + if (skip && string_is_equal(fname, skip)) + continue; + if (!frontend_driver_get_salamander_basename( salamander_name, sizeof(salamander_name))) break; if (!strncmp(fname, salamander_name, sizeof(fname))) { - if (list->size == (i + 1)) + /* Not when a core was passed over: launching salamander + * itself would only come back here. */ + if (!skip && list->size == (i + 1)) { RARCH_WARN("Entry is RetroArch Salamander itself, " "but is last entry. No choice but to set it.\n"); @@ -99,14 +104,14 @@ static void find_first_libretro_core(char *first_file, * we can find in the RetroArch cores directory. */ static void find_and_set_first_file(char *s, size_t len, - const char *ext) + const char *ext, const char *skip) { char first_file[PATH_MAX_LENGTH] = {0}; find_first_libretro_core(first_file, sizeof(first_file), - g_defaults.dirs[DEFAULT_DIR_CORE], ext); + g_defaults.dirs[DEFAULT_DIR_CORE], ext, skip); - if (!first_file || !*first_file) + if (!*first_file) { RARCH_ERR("Failed last fallback - RetroArch Salamander will exit.\n"); return; @@ -124,8 +129,10 @@ static void salamander_init(char *s, size_t len) bool config_valid = false; char config_path[PATH_MAX_LENGTH]; char config_dir[DIR_MAX_LENGTH]; + char failed[NAME_MAX_LENGTH]; config_dir[0] = '\0'; + failed[0] = '\0'; /* Get salamander config file path */ if (rarch_config_path && *rarch_config_path) @@ -138,60 +145,77 @@ static void salamander_init(char *s, size_t len) /* Ensure that config directory exists */ fill_pathname_parent_dir(config_dir, config_path, sizeof(config_dir)); - if ( (config_dir && *config_dir) - && !path_is_directory(config_dir)) + if (*config_dir && !path_is_directory(config_dir)) path_mkdir(config_dir); /* Attempt to open config file */ if ((config = config_file_new_from_path_to_string(config_path))) { char libretro_path[PATH_MAX_LENGTH]; + char pending[PATH_MAX_LENGTH]; libretro_path[0] = '\0'; + pending[0] = '\0'; if (config_get_path(config, "libretro_path", libretro_path, sizeof(libretro_path)) - && (libretro_path && *libretro_path) + && *libretro_path && !string_is_equal(libretro_path, "builtin")) { - strlcpy(s, libretro_path, len); - config_valid = true; + /* Launched last time and never started: launching it again + * would end the same way on every boot. */ + if ( config_get_path(config, SALAMANDER_CONFIG_PENDING_KEY, + pending, sizeof(pending)) + && string_is_equal(pending, libretro_path)) + { + fill_pathname_base(failed, libretro_path, sizeof(failed)); + RARCH_WARN("\"%s\" did not start when last launched; " + "looking for another core.\n", libretro_path); + } + else + { + strlcpy(s, libretro_path, len); + config_valid = true; + } } - - config_file_free(config); - config = NULL; } if (!config_valid) { char core_ext[16]; - /* No config file - search filesystem for + /* No usable entry - search filesystem for * first available core */ frontend_driver_get_core_extension( core_ext, sizeof(core_ext)); - find_and_set_first_file(s, len, core_ext); - - /* Save result to new config file */ - if (s && *s) - { - config = config_file_new_alloc(); - - if (config) - { - config_set_path(config, "libretro_path", s); - if (config_file_write(config, config_path, false)) - RARCH_DBG("Salamander config file written to \"%s\".\n", config_path); - else - RARCH_ERR("Failed to write salamander config file to \"%s\" - " - "the core will have to be located again next boot.\n", - config_path); - config_file_free(config); - } - } + find_and_set_first_file(s, len, core_ext, + *failed ? failed : NULL); } else RARCH_LOG("Start \"%s\" found in \"%s\".\n", s, FILE_PATH_SALAMANDER_CONFIG); + + if (!config) + config = config_file_new_alloc(); + + if (config) + { + /* The core to start, marked until its frontend comes up. With + * none left, both entries go, so the next boot looks again. */ + if (*s) + { + config_set_path(config, "libretro_path", s); + config_set_path(config, SALAMANDER_CONFIG_PENDING_KEY, s); + } + else + { + config_unset(config, "libretro_path"); + config_unset(config, SALAMANDER_CONFIG_PENDING_KEY); + } + if (!config_file_write(config, config_path, false)) + RARCH_ERR("Failed to write salamander config file to \"%s\".\n", + config_path); + config_file_free(config); + } } #ifdef HAVE_MAIN @@ -200,15 +224,15 @@ int salamander_main(int argc, char *argv[]) int main(int argc, char *argv[]) #endif { - /* File access for the config parser (see config_file.h). */ - config_file_set_io_default(config_file_io_filestream()); - char libretro_path[PATH_MAX_LENGTH] = {0}; void *args = NULL; struct rarch_main_wrap *wrap_args = NULL; - frontend_ctx_driver_t *frontend_ctx = (frontend_ctx_driver_t*)frontend_ctx_init_first(); + frontend_ctx_driver_t *frontend_ctx = NULL; - if (!frontend_ctx) + /* File access for the config parser (see config_file.h). */ + config_file_set_io_default(config_file_io_filestream()); + + if (!(frontend_ctx = (frontend_ctx_driver_t*)frontend_ctx_init_first())) return 0; if (frontend_ctx && frontend_ctx->init) diff --git a/retroarch.c b/retroarch.c index d7b1bd17e3..cf9ad62508 100644 --- a/retroarch.c +++ b/retroarch.c @@ -9777,6 +9777,19 @@ bool retroarch_main_init_drivers(bool staged, game_ai_init(); #endif +#if !defined(HAVE_DYNAMIC) + /* The first time this process's drivers are up, the core salamander + * launched has started. */ + { + static bool salamander_cleared = false; + if (!salamander_cleared) + { + config_clear_salamander_pending(); + salamander_cleared = true; + } + } +#endif + rarch_st.flags &= ~RARCH_FLAGS_INIT_IN_PROGRESS; return true; } diff --git a/samples/frontend/salamander_pending/Makefile b/samples/frontend/salamander_pending/Makefile new file mode 100644 index 0000000000..b57b79b34a --- /dev/null +++ b/samples/frontend/salamander_pending/Makefile @@ -0,0 +1,52 @@ +# frontend/frontend_salamander.c, the static-platform launcher, linked +# with the libretro-common units a salamander build links (see +# Makefile.vita.salamander) and a stand-in frontend driver. Builds for +# Linux and, with CC=x86_64-w64-mingw32-gcc EXE=.exe RUNNER=wine, for +# Windows. +TARGET := salamander_pending_test$(EXE) +CORE_DIR := ../../.. +LRC := $(CORE_DIR)/libretro-common + +SOURCES := salamander_pending_test.c \ + $(CORE_DIR)/frontend/frontend_salamander.c \ + $(CORE_DIR)/verbosity.c \ + $(LRC)/file/file_path.c \ + $(LRC)/file/file_path_io.c \ + $(LRC)/string/rstrtod.c \ + $(LRC)/string/stdstring.c \ + $(LRC)/lists/string_list.c \ + $(LRC)/lists/dir_list.c \ + $(LRC)/file/retro_dirent.c \ + $(LRC)/encodings/encoding_utf.c \ + $(LRC)/compat/compat_strl.c \ + $(LRC)/compat/compat_strldup.c \ + $(LRC)/compat/compat_strcasestr.c \ + $(LRC)/compat/fopen_utf8.c \ + $(LRC)/file/config_file.c \ + $(LRC)/file/config_file_io.c \ + $(LRC)/streams/file_stream.c \ + $(LRC)/vfs/vfs_implementation.c \ + $(LRC)/time/rtime.c + +CFLAGS += -std=gnu89 -Wall -g1 -O1 -DIS_SALAMANDER -DRARCH_CONSOLE \ + -DHAVE_MAIN -I$(CORE_DIR) -I$(LRC)/include +LDFLAGS += -lm + +ifneq ($(SANITIZER),) + CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS) + LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS) +endif + +all: $(TARGET) + +$(TARGET): $(SOURCES) + $(CC) $(CFLAGS) -o $@ $(SOURCES) $(LDFLAGS) + +check: all + $(RUNNER) ./$(TARGET) + +clean: + rm -rf salamander_pending_test salamander_pending_test.exe \ + salamander_pending.tmp + +.PHONY: all check clean diff --git a/samples/frontend/salamander_pending/salamander_pending_test.c b/samples/frontend/salamander_pending/salamander_pending_test.c new file mode 100644 index 0000000000..e76fb27c36 --- /dev/null +++ b/samples/frontend/salamander_pending/salamander_pending_test.c @@ -0,0 +1,190 @@ +/* frontend/frontend_salamander.c with a stand-in frontend driver: the + * core salamander launches is marked pending in its config until that + * core's frontend clears the mark. A boot that still finds the mark on + * the configured core passes over it for another core; with no other + * core left, it launches nothing and drops both entries so the boot + * after looks again. */ + +#include +#include +#include +#include +#ifdef _WIN32 +#include +#define rmdir _rmdir +#else +#include +#endif + +#include +#include +#include +#include + +#include "../../../frontend/frontend_driver.h" +#include "../../../defaults.h" +#include "../../../file_path_special.h" + +int salamander_main(int argc, char *argv[]); + +static int failures; +static char launched[PATH_MAX_LENGTH]; +static char root[PATH_MAX_LENGTH]; +static char cores[PATH_MAX_LENGTH]; +static char cfg[PATH_MAX_LENGTH]; + +#define CHECK(cond) do { if (!(cond)) { \ + fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + failures++; } } while (0) + +/* Stand-in frontend driver: the core directory and config path are the + * test's, and exitspawn records what would have been launched. */ +static void stub_get_env(int *argc, char *argv[], void *args, void *params) +{ + (void)argc; (void)argv; (void)args; (void)params; + strlcpy(g_defaults.dirs[DEFAULT_DIR_CORE], cores, + sizeof(g_defaults.dirs[DEFAULT_DIR_CORE])); + fill_pathname_join(g_defaults.path_config, root, + FILE_PATH_MAIN_CONFIG, sizeof(g_defaults.path_config)); +} + +static void stub_exitspawn(char *s, size_t len, char *args) +{ + (void)len; (void)args; + strlcpy(launched, s, sizeof(launched)); +} + +static frontend_ctx_driver_t stub_ctx; + +frontend_ctx_driver_t *frontend_ctx_init_first(void) +{ + stub_ctx.environment_get = stub_get_env; + stub_ctx.exitspawn = stub_exitspawn; + stub_ctx.ident = "test"; + return &stub_ctx; +} + +size_t frontend_driver_get_core_extension(char *s, size_t len) +{ + return strlcpy(s, "rpx", len); +} + +bool frontend_driver_get_salamander_basename(char *s, size_t len) +{ + strlcpy(s, "retroarch.rpx", len); + return true; +} + +static void touch(const char *name) +{ + char path[PATH_MAX_LENGTH]; + FILE *f; + fill_pathname_join(path, cores, name, sizeof(path)); + if ((f = fopen(path, "wb"))) + fclose(f); +} + +static void unlink_core(const char *name) +{ + char path[PATH_MAX_LENGTH]; + fill_pathname_join(path, cores, name, sizeof(path)); + remove(path); +} + +static void boot(void) +{ + char *argv[1]; + argv[0] = (char*)"retroarch"; + launched[0] = '\0'; + salamander_main(1, argv); +} + +static bool cfg_get(const char *key, char *out, size_t len) +{ + bool ok; + config_file_t *c = config_file_new_from_path_to_string(cfg); + out[0] = '\0'; + if (!c) + return false; + ok = config_get_path(c, key, out, len); + config_file_free(c); + return ok; +} + +/* What config_clear_salamander_pending() does once the core is up. */ +static void frontend_came_up(void) +{ + config_file_t *c = config_file_new_from_path_to_string(cfg); + if (!c) + return; + config_unset(c, SALAMANDER_CONFIG_PENDING_KEY); + config_file_write(c, cfg, false); + config_file_free(c); +} + +static const char *base(const char *path) +{ + return path_basename(path); +} + +int main(void) +{ + char v[PATH_MAX_LENGTH]; + + config_file_set_io_default(config_file_io_filestream()); + + strlcpy(root, "salamander_pending.tmp", sizeof(root)); + fill_pathname_join(cores, root, "cores", sizeof(cores)); + fill_pathname_join(cfg, root, FILE_PATH_SALAMANDER_CONFIG, sizeof(cfg)); + path_mkdir(cores); + remove(cfg); + touch("retroarch.rpx"); + touch("a_libretro.rpx"); + touch("b_libretro.rpx"); + + /* No config: the first core, marked pending. */ + boot(); + CHECK(string_is_equal(base(launched), "a_libretro.rpx")); + CHECK(cfg_get("libretro_path", v, sizeof(v)) + && string_is_equal(base(v), "a_libretro.rpx")); + CHECK(cfg_get(SALAMANDER_CONFIG_PENDING_KEY, v, sizeof(v)) + && string_is_equal(base(v), "a_libretro.rpx")); + + /* a came up: it is launched again, and marked again. */ + frontend_came_up(); + CHECK(!cfg_get(SALAMANDER_CONFIG_PENDING_KEY, v, sizeof(v))); + boot(); + CHECK(string_is_equal(base(launched), "a_libretro.rpx")); + + /* a never came up this time: the next boot passes it over. */ + boot(); + CHECK(string_is_equal(base(launched), "b_libretro.rpx")); + CHECK(cfg_get("libretro_path", v, sizeof(v)) + && string_is_equal(base(v), "b_libretro.rpx")); + CHECK(cfg_get(SALAMANDER_CONFIG_PENDING_KEY, v, sizeof(v)) + && string_is_equal(base(v), "b_libretro.rpx")); + + /* b is the only core left and never came up: nothing is launched, + * both entries go, and the boot after starts b again. */ + unlink_core("a_libretro.rpx"); + boot(); + CHECK(launched[0] == '\0'); + CHECK(!cfg_get("libretro_path", v, sizeof(v))); + CHECK(!cfg_get(SALAMANDER_CONFIG_PENDING_KEY, v, sizeof(v))); + boot(); + CHECK(string_is_equal(base(launched), "b_libretro.rpx")); + + unlink_core("b_libretro.rpx"); + unlink_core("retroarch.rpx"); + remove(cfg); + rmdir(cores); + rmdir(root); + + if (failures) + { + fprintf(stderr, "%d failure(s)\n", failures); + return 1; + } + printf("salamander_pending: ok\n"); + return 0; +}