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.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-10-26 04:03:49 +01:00
1 parent 06a85bc900
commit b974d0a885
7 files changed
+347 -38

No files matched your search

+25
View File
@@ -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)
+1
View File
@@ -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);
+4
View File
@@ -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"
+62 -38
View File
@@ -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)
+13
View File
@@ -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;
}
@@ -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
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#ifdef _WIN32
#include <direct.h>
#define rmdir _rmdir
#else
#include <unistd.h>
#endif
#include <boolean.h>
#include <file/config_file.h>
#include <file/file_path.h>
#include <string/stdstring.h>
#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;
}