@@ -1589,6 +1589,7 @@ dist_patch_DATA = \
%D%/packages/patches/jfsutils-add-sysmacros.patch \
%D%/packages/patches/jfsutils-gcc-compat.patch \
%D%/packages/patches/jfsutils-include-systypes.patch \
+ %D%/packages/patches/jgrf-implement-search-paths.patch \
%D%/packages/patches/john-the-ripper-jumbo-with-gcc-11.patch \
%D%/packages/patches/json-c-0.13-CVE-2020-12762.patch \
%D%/packages/patches/json-c-0.12-CVE-2020-12762.patch \
@@ -2308,7 +2308,8 @@ (define-public jgrf
(delete-file-recursively "deps/miniz")))
(sha256
(base32
- "19n6h8l3vy5g2bqvxhxwqxlg070hjz22384yisadzwl3gjkkgpxk"))))
+ "19n6h8l3vy5g2bqvxhxwqxlg070hjz22384yisadzwl3gjkkgpxk"))
+ (patches (search-patches "jgrf-implement-search-paths.patch"))))
(build-system gnu-build-system)
(arguments
(list #:tests? #f ;no test suite
@@ -2331,6 +2332,13 @@ (define-public jgrf
sdl2
zlib
`(,zstd "lib")))
+ (native-search-paths
+ (list (search-path-specification
+ (variable "JOLLYGOOD_CORE_DIRS")
+ (files '("lib/jollygood")))
+ (search-path-specification
+ (variable "JOLLYGOOD_ASSETS_DIRS")
+ (files '("share/jollygood")))))
(home-page "https://gitlab.com/jgemu/jgrf")
(synopsis "Jolly Good Reference Frontend")
(description "The Jolly Good Reference Frontend (accessible via the
new file mode 100644
@@ -0,0 +1,302 @@
+Upstream status: https://gitlab.com/jgemu/jgrf/-/merge_requests/61
+
+Add support for searching core files and core files assets via the
+JOLLYGOOD_CORE_DIRS and JOLLYGOOD_ASSETS_DIRS environment variables.
+
+diff --git a/Makefile b/Makefile
+index ed6eb1c..fb0709c 100644
+--- a/Makefile
++++ b/Makefile
+@@ -20,7 +20,9 @@ INCLUDES = -I$(DEPDIR) $(CFLAGS_JG) $(CFLAGS_EPOXY) $(CFLAGS_MINIZ) \
+
+ LIBS = -lm
+
+-DEFINES :=
++# Define the '_GNU_SOURCE' macro to make the `strdup' function
++# available on GNU systems.
++DEFINES := -D_GNU_SOURCE
+
+ # Conditions for DEFINES
+ ifneq ($(OS), Windows_NT)
+diff --git a/deps/ezmenu.h b/deps/ezmenu.h
+index 8400a02..6c8e14b 100644
+--- a/deps/ezmenu.h
++++ b/deps/ezmenu.h
+@@ -66,10 +66,10 @@ enum ezmenu_input {
+
+ static void ezmenu_init(struct ezmenu *m, int hres, int vres,
+ int fontw, int fonth) {
+- memset(m, 0, sizeof *m);
++ memset(m, 0, sizeof(*m));
+ m->w = hres/fontw;
+ m->h = vres/fonth;
+- m->vislines = calloc(sizeof(char*), m->h);
++ m->vislines = calloc(m->h, sizeof(char*));
+ }
+
+ static void ezmenu_setlines(struct ezmenu *m, char**lines, unsigned linecount) {
+diff --git a/jollygood.6 b/jollygood.6
+index 9f36407..17fef9c 100644
+--- a/jollygood.6
++++ b/jollygood.6
+@@ -336,6 +336,14 @@ The directory for user-specific data files.
+ This path is used to find data files used by the cores.
+ Set by default to
+ .Pa $HOME/.local/share/jollygood/ .
++.It JOLLYGOOD_CORE_DIRS
++Colon-separated (or semicolon, on Windows) directories containing core
++files. If set, these core files directories take precedence over the
++default ones.
++.It JOLLYGOOD_ASSETS_DIRS
++Colon-separated (or semicolon, on Windows) directories containing core
++assets files. If set, these core assets directories take precedence
++over the default ones.
+ .El
+ .Sh EXAMPLES
+ .Bl -tag -width indent
+diff --git a/src/jgrf.c b/src/jgrf.c
+index 3a40d2d..12eede3 100644
+--- a/src/jgrf.c
++++ b/src/jgrf.c
+@@ -161,6 +161,12 @@ static void mkdirr(const char *dir) {
+ #endif
+ }
+
++#if defined(__MINGW32__) || defined(__MINGW64__)
++// Avoid a deprecation warning on Windows, where strdup exists but is
++// deprecated in favor of _strdup.
++#define strdup _strdup
++#endif
++
+ // Create user directories
+ static void jgrf_mkdirs(void) {
+ mkdirr(gdata.configpath);
+@@ -1096,6 +1102,88 @@ void jgrf_frametime(double frametime) {
+ corefps = frametime + 0.5;
+ }
+
++// Wrapper that logs and errors in case of realloc problems.
++static void* jgrf_realloc(void* array, const size_t size) {
++ void* new_array;
++ if (size > 0 && !(new_array = realloc(array, size)))
++ jgrf_log(JG_LOG_ERR, "Realloc failure\n");
++ return new_array;
++}
++
++static void jgrf_strip_trailing_sep(char* word) {
++ int end = strlen(word) - 1;
++ while (word[end] == SEP) {
++ word[end] = '\0';
++ end -= 1;
++ }
++}
++
++// Tokenize PATH based on the platform path separator and return an
++// array of strings, or NULL if there nothing could be tokenized. The
++// count of the number of items is written at the location pointed by
++// the COUNT pointer. The returned array is dynamically allocated and
++// should be freed when no longer needed, along its inner strings.
++// `transform' can be provided to manipulate the recovered path items;
++// it must be the pointer of a procedure accepting a single string
++// (char*) path item as argument or NULL.
++static char** tokenize_path(int* count, const char* path,
++ void (*transform) (char*)) {
++ int length = 10;
++ int index = 0;
++ char* save_ptr;
++ char** items = jgrf_realloc(NULL, sizeof(char*) * length);
++ char* item;
++ char pathsep_str[2] = {PATHSEP, '\0'};
++ char* wr_path = strdup(path);
++ if (!wr_path)
++ jgrf_log(JG_LOG_ERR, "strdup memory allocation failure\n");
++
++ item = strtok_r(wr_path, pathsep_str, &save_ptr);
++ while (item) {
++ if (transform)
++ transform(item);
++
++ // Resize the array if needed.
++ if (index >= length) {
++ length += 10;
++ items = jgrf_realloc(items, sizeof(char*) * length);
++ }
++
++ // Assign the component.
++ items[index] = strdup(item);
++ if (!items[index])
++ jgrf_log(JG_LOG_ERR, "stdup memory allocation failure\n");
++
++ index += 1;
++ item = strtok_r(NULL, pathsep_str, &save_ptr);
++ }
++ free(wr_path);
++ jgrf_realloc(items, sizeof(char*) * index);
++
++ *count = index;
++ return items;
++}
++
++// Look if a core named NAME exists under the directory CORE_DIR. Set
++// CORE_FILE as a side-effect. Return 1 if found, 0 otherwise.
++static int search_core_file(char* core_file, size_t max_length,
++ const char* core_dir, const char* name) {
++ struct stat fbuf;
++ snprintf(core_file, max_length, "%s%c%s.%s",
++ core_dir, SEP, name, SOEXT);
++ return !stat(core_file, &fbuf);
++}
++
++// Look if an core assets directory for core named NAME exists under
++// the directory ASSETS_DIR. Set CORE_ASSETS as a side-effect. Return
++// 1 if found, 0 otherwise.
++static int search_core_assets(char* core_assets, size_t max_length,
++ const char* assets_dir, const char* name) {
++ struct stat fbuf;
++ snprintf(core_assets, max_length, "%s%c%s", assets_dir, SEP, name);
++ return !stat(core_assets, &fbuf);
++}
++
+ int main(int argc, char *argv[]) {
+ if (argc < 2) {
+ jgrf_cli_usage(argv[0]);
+@@ -1188,7 +1276,7 @@ int main(int argc, char *argv[]) {
+ jg_get_coreinfo("")->name);
+
+ #if defined(LIBDIR) && defined(DATADIR) // Check for core assets system-wide
+- char coreassets[256];
++ char coreassets[384];
+ snprintf(coreassets, sizeof(coreassets),
+ "%s%cjollygood%c%s", DATADIR, SEP, SEP, gdata.corename);
+
+@@ -1204,7 +1292,7 @@ int main(int argc, char *argv[]) {
+ }
+ else if (!jgrf_core_default())
+ jgrf_log(JG_LOG_ERR,
+- "Cannot detect default core, or invalid file. Exiting...\n");
++ "Cannot detect default core, or invalid file. Exiting...\n");
+
+ // Set the core path to the local core path
+ char corepath[384];
+@@ -1222,20 +1310,85 @@ int main(int argc, char *argv[]) {
+ }
+ #if defined(LIBDIR) && defined(DATADIR) // Check for the core system-wide
+ else {
+- snprintf(corepath, sizeof(corepath), "%s%cjollygood%c%s.%s",
+- LIBDIR, SEP, SEP, gdata.corename, SOEXT);
+-
+- // If it was found, set the core assets path
+- if (stat(corepath, &fbuf) == 0) {
+- snprintf(gdata.coreassets, sizeof(gdata.coreassets),
+- "%s%cjollygood%c%s", DATADIR, SEP, SEP, gdata.corename);
+- corefound = 1;
++ int core_assets_found = 0;
++ int count = 0;
++
++ // Look for the core file in the JOLLYGOOD_CORE_DIRS search
++ // path.
++ const char* core_dirs_env = getenv("JOLLYGOOD_CORE_DIRS");
++ if (core_dirs_env) {
++ char** core_dirs = tokenize_path(&count, core_dirs_env,
++ jgrf_strip_trailing_sep);
++ for (int i=0; i < count; ++i) {
++ const char* dir = core_dirs[i];
++ if (search_core_file(corepath, sizeof corepath,
++ dir, gdata.corename)) {
++ corefound = 1;
++ break;
++ }
++ }
++ // Free all the allocated strings.
++ for (int i=0; i < count; ++i)
++ free(core_dirs[i]);
++ free(core_dirs);
++ }
++
++ // Look in the configured LIBDIR as a fallback.
++ if (!corefound) {
++ char internal_core_dir[256];
++ snprintf(internal_core_dir, sizeof internal_core_dir, "%s%c%s",
++ LIBDIR, SEP, "jollygood");
++ if (search_core_file(corepath, sizeof corepath,
++ internal_core_dir, gdata.corename)) {
++ corefound = 1;
++ } else {
++ jgrf_log(JG_LOG_ERR,
++ "Could not locate core file. Exiting...\n");
++ }
+ }
+- }
+
+- // If no core was found, there is no reason to keep the program running
+- if (!corefound)
+- jgrf_log(JG_LOG_ERR, "Failed to locate core. Exiting...\n");
++ // If it was found, set the core assets path. First consider
++ // the JOLLYGOOD_ASSETS_DIRS search path.
++ if (corefound) {
++ const char* assets_dirs_env = getenv("JOLLYGOOD_ASSETS_DIRS");
++ if (assets_dirs_env) {
++ char** assets_dirs = tokenize_path(&count, assets_dirs_env,
++ jgrf_strip_trailing_sep);
++ for (int i=0; i < count; ++i) {
++ const char* dir = assets_dirs[i];
++ if (search_core_assets(gdata.coreassets,
++ sizeof gdata.coreassets,
++ dir, gdata.corename)) {
++ core_assets_found = 1;
++ break;
++ }
++ }
++ for (int i=0; i < count; ++i)
++ free(assets_dirs[i]);
++ free(assets_dirs);
++ }
++
++ // Look in the configured DATADIR as a fallback.
++ if (!core_assets_found) {
++ char internal_core_assets_dir[256];
++ snprintf(internal_core_assets_dir,
++ sizeof internal_core_assets_dir,
++ "%s%c%s", DATADIR, SEP, "jollygood");
++ if (search_core_assets(gdata.coreassets,
++ sizeof gdata.coreassets,
++ internal_core_assets_dir,
++ gdata.corename)) {
++ core_assets_found = 1;
++ } else {
++ // Not all emulators have core assets,
++ // e.g. 'cega', so this is not a critical
++ // condition.
++ jgrf_log(JG_LOG_INF, "No assets directory for core %s\n",
++ gdata.corename);
++ }
++ }
++ }
++ }
+ #endif // defined(LIBDIR) && defined(DATADIR)
+
+ #endif // JGRF_STATIC
+diff --git a/src/jgrf.h b/src/jgrf.h
+index b3f4627..57bba0e 100644
+--- a/src/jgrf.h
++++ b/src/jgrf.h
+@@ -45,8 +45,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ #if defined(_WIN32) || defined(__MINGW32__) || defined(__MINGW64__)
+ #define SEP '\\'
++ #define PATHSEP ';'
+ #else
+ #define SEP '/'
++ #define PATHSEP ':'
+ #endif
+
+ typedef struct jgrf_gdata_t { // Global Data
+@@ -59,7 +61,7 @@ typedef struct jgrf_gdata_t { // Global Data
+ char coreversion[32]; // Core Version
+ char gamename[128]; // Internally used game name
+ char gamefname[128]; // Internally used game name with extension
+- char coreassets[256]; // Core asset path
++ char coreassets[384]; // Core asset path
+ char userassets[128]; // User asset path
+ char biospath[128]; // BIOS path
+ char cheatpath[128]; // Cheat path