diff --git a/src/86box.c b/src/86box.c index db0182d84..4eae7ab02 100644 --- a/src/86box.c +++ b/src/86box.c @@ -32,6 +32,7 @@ #ifndef _WIN32 #include +#include #endif #ifdef __APPLE__ #include @@ -125,6 +126,7 @@ uint64_t unique_id = 0; uint64_t source_hwnd = 0; #endif char rom_path[1024] = { '\0'}; /* (O) full path to ROMs */ +rom_path_t rom_paths = { "", NULL }; /* (O) full paths to ROMs */ char log_path[1024] = { '\0'}; /* (O) full path of logfile */ char vm_name[1024] = { '\0'}; /* (O) display name of the VM */ @@ -383,6 +385,48 @@ pc_log(const char *fmt, ...) #define pc_log(fmt, ...) #endif +void +add_rom_path(const char* path) +{ + static char cwd[1024]; + memset(cwd, 0x00, sizeof(cwd)); + rom_path_t* cur_rom_path = &rom_paths; + while (cur_rom_path->next != NULL) { + cur_rom_path = cur_rom_path->next; + } + if (!plat_path_abs((char*)path)) { + /* + * This looks like a relative path. + * + * Add it to the current working directory + * to convert it (back) to an absolute path. + */ + plat_getcwd(cwd, 1024); + plat_path_slash(cwd); + snprintf(cur_rom_path->rom_path, 1024, "%s%s%c", cwd, path, 0); + } + else { + /* + * The user-provided path seems like an + * absolute path, so just use that. + */ + strncpy(cur_rom_path->rom_path, path, 1024); + } + plat_path_slash(cur_rom_path->rom_path); + cur_rom_path->next = calloc(1, sizeof(rom_path_t)); +} + +// Copied over from Unix code, which in turn is lifted from musl. Needed for parsing XDG_DATA_DIRS. +static char *local_strsep(char **str, const char *sep) +{ + char *s = *str, *end; + if (!s) return NULL; + end = s + strcspn(s, sep); + if (*end) *end++ = 0; + else end = 0; + *str = end; + return s; +} /* * Perform initial startup of the PC. @@ -496,6 +540,7 @@ usage: if ((c+1) == argc) goto usage; strcpy(path2, argv[++c]); + add_rom_path(path2); } else if (!strcasecmp(argv[c], "--config") || !strcasecmp(argv[c], "-C")) { if ((c+1) == argc) goto usage; @@ -591,26 +636,44 @@ usage: plat_dir_create(usr_path); } - if (path2[0] == '\0') { + if (vmrp) { + char vmrppath[1024] = { 0 }; + strcpy(vmrppath, usr_path); + plat_path_slash(vmrppath); + strcat(vmrppath, "roms"); + plat_path_slash(vmrppath); + add_rom_path(vmrppath); + if (path2[0] == '\0') { + strcpy(path2, vmrppath); + } + } + + { + char default_rom_path[1024] = { 0 }; #if defined(__APPLE__) - getDefaultROMPath(path2); + getDefaultROMPath(default_rom_path); #elif !defined(_WIN32) appimage = getenv("APPIMAGE"); if (appimage && (appimage[0] != '\0')) { - plat_get_dirname(path2, appimage); - plat_path_slash(path2); - strcat(path2, "roms"); - plat_path_slash(path2); + plat_get_dirname(default_rom_path, appimage); + plat_path_slash(default_rom_path); + strcat(default_rom_path, "roms"); + plat_path_slash(default_rom_path); } #endif + if (default_rom_path[0] == '\0') { + plat_getcwd(default_rom_path, 1024); + plat_path_slash(default_rom_path); + snprintf(default_rom_path, 1024, "%s%s%c", default_rom_path, "roms", 0); + plat_path_slash(default_rom_path); + } + add_rom_path(default_rom_path); + if (path2[0] == '\0') { + strcpy(path2, default_rom_path); + } } - if (vmrp && (path2[0] == '\0')) { - strcpy(path2, usr_path); - plat_path_slash(path2); - strcat(path2, "roms"); - plat_path_slash(path2); - } + plat_init_rom_paths(); /* * If the user provided a path for ROMs, use that @@ -708,8 +771,13 @@ usage: pclog("# VM: %s\n#\n", vm_name); pclog("# Emulator path: %s\n", exe_path); pclog("# Userfiles path: %s\n", usr_path); - if (rom_path[0] != '\0') - pclog("# ROM path: %s\n", rom_path); + if (rom_paths.next) { + rom_path_t* cur_rom_path = &rom_paths; + while (cur_rom_path->next) { + pclog("# ROM path: %s\n", cur_rom_path->rom_path); + cur_rom_path = cur_rom_path->next; + } + } else #ifndef _WIN32 pclog("# ROM path: %sroms/\n", exe_path); diff --git a/src/include/86box/plat.h b/src/include/86box/plat.h index 71ad89927..ad7006165 100644 --- a/src/include/86box/plat.h +++ b/src/include/86box/plat.h @@ -104,6 +104,7 @@ extern int plat_getcwd(char *bufp, int max); extern int plat_chdir(char *path); extern void plat_tempfile(char *bufp, char *prefix, char *suffix); extern void plat_get_exe_name(char *s, int size); +extern void plat_init_rom_paths(); extern char *plat_get_basename(const char *path); extern void plat_get_dirname(char *dest, const char *path); extern char *plat_get_filename(char *s); diff --git a/src/include/86box/rom.h b/src/include/86box/rom.h index dfea9f0b5..04fe94422 100644 --- a/src/include/86box/rom.h +++ b/src/include/86box/rom.h @@ -41,6 +41,15 @@ typedef struct { } rom_t; +typedef struct rom_path_t { + char rom_path[1024]; + struct rom_path_t* next; +} rom_path_t; + +extern rom_path_t rom_paths; + +extern void add_rom_path(const char* path); + extern uint8_t rom_read(uint32_t addr, void *p); extern uint16_t rom_readw(uint32_t addr, void *p); extern uint32_t rom_readl(uint32_t addr, void *p); diff --git a/src/mem/rom.c b/src/mem/rom.c index 30e873a4e..3dcbfe568 100644 --- a/src/mem/rom.c +++ b/src/mem/rom.c @@ -69,11 +69,19 @@ rom_fopen(char *fn, char *mode) fn2 = (char *) malloc(strlen(fn) + 1); memcpy(fn2, fn, strlen(fn) + 1); - if (rom_path[0] != '\0') { + if (rom_paths.next) { + rom_path_t* cur_rom_path = &rom_paths; memset(fn2, 0x00, strlen(fn) + 1); memcpy(fn2, &(fn[5]), strlen(fn) - 4); - plat_append_filename(temp, rom_path, fn2); + while (cur_rom_path->next) { + memset(temp, 0, sizeof(temp)); + plat_append_filename(temp, cur_rom_path->rom_path, fn2); + if (rom_present(temp)) { + break; + } + cur_rom_path = cur_rom_path->next; + } } else { /* Make sure to make it a backslash, just in case there's malformed code calling us that assumes Windows. */ @@ -97,17 +105,54 @@ rom_fopen(char *fn, char *mode) int rom_getfile(char *fn, char *s, int size) { - FILE *f; + char temp[1024] = {'\0'}; + char *fn2; + int retval = 0; - plat_append_filename(s, exe_path, fn); + if ((strstr(fn, "roms/") == fn) || (strstr(fn, "roms\\") == fn)) { + /* Relative path */ + fn2 = (char *) malloc(strlen(fn) + 1); + memcpy(fn2, fn, strlen(fn) + 1); - f = plat_fopen(s, "rb"); - if (f != NULL) { - (void)fclose(f); - return(1); + if (rom_paths.next) { + rom_path_t* cur_rom_path = &rom_paths; + memset(fn2, 0x00, strlen(fn) + 1); + memcpy(fn2, &(fn[5]), strlen(fn) - 4); + + while (cur_rom_path->next) { + memset(temp, 0, sizeof(temp)); + plat_append_filename(temp, cur_rom_path->rom_path, fn2); + if (rom_present(temp)) { + strncpy(s, temp, size); + retval = 1; + break; + } + cur_rom_path = cur_rom_path->next; + } + } else { + /* Make sure to make it a backslash, just in case there's malformed + code calling us that assumes Windows. */ + if (fn2[4] == '\\') + fn2[4] = '/'; + + plat_append_filename(temp, exe_path, fn2); + if (rom_present(temp)) { + strncpy(s, temp, size); + retval = 1; + } } - return(0); + free(fn2); + fn2 = NULL; + } else { + /* Absolute path */ + if (rom_present(fn)) { + strncpy(s, fn, size); + retval = 1; + } + } + + return(retval); } diff --git a/src/qt/qt_platform.cpp b/src/qt/qt_platform.cpp index ff7b99c93..e6e3256d3 100644 --- a/src/qt/qt_platform.cpp +++ b/src/qt/qt_platform.cpp @@ -88,6 +88,8 @@ extern "C" { #include <86box/timer.h> #include <86box/nvr.h> #include <86box/plat_dynld.h> +#include <86box/mem.h> +#include <86box/rom.h> #include <86box/config.h> #include <86box/ui.h> #include <86box/discord.h> @@ -583,3 +585,63 @@ plat_chdir(char *path) { return QDir::setCurrent(QString(path)) ? 0 : -1; } + +void +plat_init_rom_paths() +{ +#if defined __APPLE__ + QDir::root().mkpath(QStringLiteral("%1/Documents/86Box/roms/").arg(QDir::homePath())); + add_rom_path(QStringLiteral("%1/Documents/86Box/roms/").arg(QDir::homePath()).toUtf8().constData()); +#elif !defined _WIN32 + if (getenv("XDG_DATA_HOME")) { + char xdg_rom_path[1024 + 1] = { 0 }; + strncpy(xdg_rom_path, getenv("XDG_DATA_HOME"), 1024); + plat_path_slash(xdg_rom_path); + strncat(xdg_rom_path, "86Box/", 1024); + + if (!plat_dir_check(xdg_rom_path)) + plat_dir_create(xdg_rom_path); + strcat(xdg_rom_path, "roms/"); + + if (!plat_dir_check(xdg_rom_path)) + plat_dir_create(xdg_rom_path); + add_rom_path(xdg_rom_path); + } else { + char home_rom_path[1024] = { 0 }; + snprintf(home_rom_path, 1024, "%s/.local/share/86Box/", getenv("HOME") ? getenv("HOME") : QDir::homePath().toUtf8().constData()); + + if (!plat_dir_check(home_rom_path)) + plat_dir_create(home_rom_path); + strcat(home_rom_path, "roms/"); + + if (!plat_dir_check(home_rom_path)) + plat_dir_create(home_rom_path); + add_rom_path(home_rom_path); + } + if (getenv("XDG_DATA_DIRS")) { + char* xdg_rom_paths = strdup(getenv("XDG_DATA_DIRS")); + char* xdg_rom_paths_orig = xdg_rom_paths; + char* cur_xdg_rom_path = NULL; + if (xdg_rom_paths) { + while (xdg_rom_paths[strlen(xdg_rom_paths) - 1] == ':') { + xdg_rom_paths[strlen(xdg_rom_paths) - 1] = '\0'; + } + QStringList path_list = QString(xdg_rom_paths).split(":"); + for (auto& cur_path : path_list) { + if (cur_path.right(1) != '/') + cur_path.push_back('/'); + add_rom_path((cur_path + "86Box/roms").toUtf8().constData()); + } + } + free(xdg_rom_paths_orig); + } else { + add_rom_path("/usr/local/share/86Box/roms/"); + add_rom_path("/usr/share/86Box/roms/"); + } +#elif _WIN32 + auto appDataDir = QDir(qEnvironmentVariable("LOCALAPPDATA")); + appDataDir.mkdir("86Box"); + appDataDir.mkdir("86Box/roms"); + add_rom_path((appDataDir.path().replace("\\","/") + "/86Box/roms").toUtf8().constData()); +#endif +} diff --git a/src/unix/unix.c b/src/unix/unix.c index 1f60ad565..5f6429b89 100644 --- a/src/unix/unix.c +++ b/src/unix/unix.c @@ -21,6 +21,8 @@ #include #include <86box/86box.h> +#include <86box/mem.h> +#include <86box/rom.h> #include <86box/keyboard.h> #include <86box/mouse.h> #include <86box/config.h> @@ -744,6 +746,66 @@ plat_pause(int p) } } +void +plat_init_rom_paths() +{ +#ifndef __APPLE__ + if (getenv("XDG_DATA_HOME")) { + char xdg_rom_path[1024] = { 0 }; + strncpy(xdg_rom_path, getenv("XDG_DATA_HOME"), 1024); + plat_path_slash(xdg_rom_path); + strncat(xdg_rom_path, "86Box/", 1024); + + if (!plat_dir_check(xdg_rom_path)) + plat_dir_create(xdg_rom_path); + strcat(xdg_rom_path, "roms/"); + + if (!plat_dir_check(xdg_rom_path)) + plat_dir_create(xdg_rom_path); + add_rom_path(xdg_rom_path); + } else { + char home_rom_path[1024] = { 0 }; + snprintf(home_rom_path, 1024, "%s/.local/share/86Box/", getenv("HOME") ? getenv("HOME") : getpwuid(getuid())->pw_dir); + + if (!plat_dir_check(home_rom_path)) + plat_dir_create(home_rom_path); + strcat(home_rom_path, "roms/"); + + if (!plat_dir_check(home_rom_path)) + plat_dir_create(home_rom_path); + add_rom_path(home_rom_path); + } + if (getenv("XDG_DATA_DIRS")) { + char* xdg_rom_paths = strdup(getenv("XDG_DATA_DIRS")); + char* xdg_rom_paths_orig = xdg_rom_paths; + char* cur_xdg_rom_path = NULL; + if (xdg_rom_paths) { + while (xdg_rom_paths[strlen(xdg_rom_paths) - 1] == ':') { + xdg_rom_paths[strlen(xdg_rom_paths) - 1] = '\0'; + } + while ((cur_xdg_rom_path = local_strsep(&xdg_rom_paths, ";")) != NULL) { + char real_xdg_rom_path[1024] = { '\0' }; + strcat(real_xdg_rom_path, cur_xdg_rom_path); + plat_path_slash(real_xdg_rom_path); + strcat(real_xdg_rom_path, "86Box/roms/"); + add_rom_path(real_xdg_rom_path); + } + } + free(xdg_rom_paths_orig); + } else { + add_rom_path("/usr/local/share/86Box/roms/"); + add_rom_path("/usr/share/86Box/roms/"); + } +#else + char home_rom_path[1024] = { '\0' }; + snprintf(home_rom_path, 1024, "%s/Documents/86Box/", getenv("HOME") ? getenv("HOME") : getpwuid(getuid())->pw_dir); + plat_dir_create(home_rom_path); + strcat(home_rom_path, "roms/"); + plat_dir_create(home_rom_path); + add_rom_path(home_rom_path); +#endif +} + bool process_media_commands_3(uint8_t* id, char* fn, uint8_t* wp, int cmdargc) { bool err = false; diff --git a/src/win/win.c b/src/win/win.c index f4b906d7d..06db20bad 100644 --- a/src/win/win.c +++ b/src/win/win.c @@ -45,6 +45,8 @@ #include <86box/timer.h> #include <86box/nvr.h> #include <86box/video.h> +#include <86box/mem.h> +#include <86box/rom.h> #define GLOBAL #include <86box/plat.h> #include <86box/ui.h> @@ -910,6 +912,30 @@ plat_mmap(size_t size, uint8_t executable) } +void +plat_init_rom_paths() +{ + wchar_t appdata_dir[1024] = { L'\0' }; + + if (_wgetenv("LOCALAPPDATA") && _wgetenv("LOCALAPPDATA")[0] != L'\0') { + char appdata_dir_a[1024] = { '\0' }; + size_t len = 0; + wcsncpy(appdata_dir, _wgetenv("LOCALAPPDATA"), 1024); + len = wcslen(appdata_dir); + if (appdata_dir[len - 1] != L'\\') { + appdata_dir[len] = L'\\'; + appdata_dir[len + 1] = L'\0'; + } + wcscat(appdata_dir, "86box"); + CreateDirectoryW(appdata_dir, NULL); + wcscat(appdata_dir, "\\roms"); + CreateDirectoryW(appdata_dir, NULL); + wcscat(appdata_dir, "\\"); + c16stombs(appdata_dir_a, appdata_dir, 1024); + add_rom_path(appdata_dir_a); + } +} + void plat_munmap(void *ptr, size_t size) {