From b974d0a885c12ecd11e07da986709208568a66fe Mon Sep 17 00:00:00 2001 From: LibretroAdmin Date: Mon, 26 Oct 2026 04:06:09 +0100 Subject: [PATCH] salamander: pass over a core that did not start Salamander marks the core it launches in its config (libretro_path_pending) and the frontend clears the mark once its drivers are first up. A boot that still finds the mark on the configured core launches the first other core in the cores directory instead, so one core that dies on startup no longer takes every later boot down with it; with no other core, nothing is launched and both entries are dropped so the next boot looks again. A passed-over core never falls back to salamander itself. samples/frontend/salamander_pending boots salamander against a cores directory with a stand-in frontend driver and checks the mark, the pass-over, and the empty fallback. --- configuration.c | 25 +++ configuration.h | 1 + file_path_special.h | 4 + frontend/frontend_salamander.c | 100 +++++---- retroarch.c | 13 ++ samples/frontend/salamander_pending/Makefile | 52 +++++ .../salamander_pending_test.c | 190 ++++++++++++++++++ 7 files changed, 347 insertions(+), 38 deletions(-) create mode 100644 samples/frontend/salamander_pending/Makefile create mode 100644 samples/frontend/salamander_pending/salamander_pending_test.c 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; +}