diff --git a/autosave.c b/autosave.c index 2f1fb6109d..5e0fe3fb95 100644 --- a/autosave.c +++ b/autosave.c @@ -122,6 +122,9 @@ void autosave_unlock(autosave_t *handle) void autosave_free(autosave_t *handle) { + if (!handle) + return; + slock_lock(handle->cond_lock); handle->quit = true; slock_unlock(handle->cond_lock); @@ -139,7 +142,7 @@ void autosave_free(autosave_t *handle) void lock_autosave(void) { unsigned i; - for (i = 0; i < ARRAY_SIZE(g_extern.autosave); i++) + for (i = 0; i < g_extern.num_autosave; i++) { if (g_extern.autosave[i]) autosave_lock(g_extern.autosave[i]); @@ -149,7 +152,7 @@ void lock_autosave(void) void unlock_autosave(void) { unsigned i; - for (i = 0; i < ARRAY_SIZE(g_extern.autosave); i++) + for (i = 0; i < g_extern.num_autosave; i++) { if (g_extern.autosave[i]) autosave_unlock(g_extern.autosave[i]); diff --git a/dynamic.c b/dynamic.c index 50b3a96611..636ed1d3bd 100644 --- a/dynamic.c +++ b/dynamic.c @@ -185,6 +185,21 @@ void libretro_free_system_info(struct retro_system_info *info) memset(info, 0, sizeof(*info)); } +const struct retro_game_special_info *libretro_find_subsystem_info(const struct retro_game_special_info *info, unsigned num_info, + const char *ident) +{ + unsigned i; + for (i = 0; i < num_info; i++) + { + if (!strcmp(info[i].ident, ident)) + return &info[i]; + else if (!strcmp(info[i].desc, ident)) // Doesn't hurt + return &info[i]; + } + + return NULL; +} + static bool find_first_libretro(char *path, size_t size, const char *dir, const char *rom_path) { @@ -417,6 +432,7 @@ void uninit_libretro_sym(void) } // No longer valid. + free(g_extern.system.special); memset(&g_extern.system, 0, sizeof(g_extern.system)); #ifdef HAVE_CAMERA g_extern.camera_active = false; @@ -914,6 +930,23 @@ bool rarch_environment_cb(unsigned cmd, void *data) return driver_update_system_av_info((const struct retro_system_av_info*)data); } + case RETRO_ENVIRONMENT_SET_SPECIAL_GAME_TYPES: + { + RARCH_LOG("Environ SET_SPECIAL_GAME_TYPES.\n"); + unsigned i; + const struct retro_game_special_info *info = (const struct retro_game_special_info*)data; + for (i = 0; info[i].ident; i++) + RARCH_LOG("Special game type: %s (ident: %s)\n", info[i].desc, info[i].ident); + + free(g_extern.system.special); + g_extern.system.special = (struct retro_game_special_info*)calloc(i, sizeof(*g_extern.system.special)); + if (!g_extern.system.special) + return false; + + memcpy(g_extern.system.special, info, i * sizeof(*g_extern.system.special)); + break; + } + case RETRO_ENVIRONMENT_EXEC: case RETRO_ENVIRONMENT_EXEC_ESCAPE: diff --git a/dynamic.h b/dynamic.h index 0b1309d706..cc83cd06f4 100644 --- a/dynamic.h +++ b/dynamic.h @@ -64,6 +64,8 @@ void libretro_free_system_info(struct retro_system_info *info); // Transforms a library id to a name suitable as a pathname. void libretro_get_current_core_pathname(char *name, size_t size); +const struct retro_game_special_info *libretro_find_subsystem_info(const struct retro_game_special_info *info, unsigned num_info, const char *ident); + extern void (*pretro_init)(void); extern void (*pretro_deinit)(void); diff --git a/file.c b/file.c index e41f127572..a118142f6a 100644 --- a/file.c +++ b/file.c @@ -143,35 +143,8 @@ static ssize_t read_rom_file(const char *path, void **buf) return ret; } - -static const char *ramtype2str(int type) -{ - switch (type) - { - case RETRO_MEMORY_SAVE_RAM: - case RETRO_MEMORY_SNES_GAME_BOY_RAM: - case RETRO_MEMORY_SNES_BSX_RAM: - return ".srm"; - - case RETRO_MEMORY_RTC: - case RETRO_MEMORY_SNES_GAME_BOY_RTC: - return ".rtc"; - - case RETRO_MEMORY_SNES_BSX_PRAM: - return ".pram"; - - case RETRO_MEMORY_SNES_SUFAMI_TURBO_A_RAM: - return ".aram"; - case RETRO_MEMORY_SNES_SUFAMI_TURBO_B_RAM: - return ".bram"; - - default: - return ""; - } -} - // Attempt to save valuable RAM data somewhere ... -static void dump_to_file_desperate(const void *data, size_t size, int type) +static void dump_to_file_desperate(const void *data, size_t size, unsigned type) { #if defined(_WIN32) && !defined(_XBOX) const char *base = getenv("APPDATA"); @@ -185,14 +158,13 @@ static void dump_to_file_desperate(const void *data, size_t size, int type) goto error; char path[PATH_MAX]; - snprintf(path, sizeof(path), "%s/RetroArch-recovery-", base); + snprintf(path, sizeof(path), "%s/RetroArch-recovery-%u", base, type); char timebuf[PATH_MAX]; time_t time_; time(&time_); strftime(timebuf, sizeof(timebuf), "%Y-%m-%d-%H-%M-%S", localtime(&time_)); strlcat(path, timebuf, sizeof(path)); - strlcat(path, ramtype2str(type), sizeof(path)); if (write_file(path, data, size)) RARCH_WARN("Succeeded in saving RAM data to \"%s\".\n", path); @@ -248,6 +220,7 @@ bool load_state(const char *path) bool ret = true; RARCH_LOG("State size: %u bytes.\n", (unsigned)size); +#if 0 void *block_buf[2] = {NULL, NULL}; int block_type[2] = {-1, -1}; size_t block_size[2] = {0}; @@ -315,6 +288,8 @@ bool load_state(const char *path) for (i = 0; i < 2; i++) if (block_buf[i]) free(block_buf[i]); +#endif + ret = pretro_unserialize(buf, size); free(buf); return ret; @@ -362,175 +337,163 @@ void save_ram_file(const char *path, int type) } } -#define MAX_ROMS 4 - -static bool load_roms(unsigned rom_type, const char **rom_paths, size_t roms) +static bool load_roms(const struct retro_game_special_info *special, const struct string_list *roms) { - size_t i; + unsigned i; bool ret = true; - if (roms == 0) + struct retro_game_info *info = (struct retro_game_info*)calloc(roms->size, sizeof(*info)); + if (!info) return false; - if (roms > MAX_ROMS) - return false; - - void *rom_buf[MAX_ROMS] = {NULL}; - long rom_len[MAX_ROMS] = {0}; - struct retro_game_info info[MAX_ROMS] = {{NULL}}; - - if (!g_extern.system.info.need_fullpath) + for (i = 0; i < roms->size; i++) { - RARCH_LOG("Loading ROM file: %s.\n", rom_paths[0]); - if ((rom_len[0] = read_rom_file(rom_paths[0], &rom_buf[0])) == -1) + const char *path = roms->elems[i].data; + int attr = roms->elems[i].attr.i; + + bool need_fullpath = attr & 2; + bool require_rom = attr & 4; + + if (require_rom && !*path) { - RARCH_ERR("Could not read ROM file.\n"); + RARCH_LOG("libretro core requires a ROM, but none were provided.\n"); ret = false; goto end; } - RARCH_LOG("ROM size: %u bytes.\n", (unsigned)rom_len[0]); - } - else - RARCH_LOG("ROM loading skipped. Implementation will load it on its own.\n"); + info[i].path = *path ? path : NULL; - info[0].path = rom_paths[0]; - info[0].data = rom_buf[0]; - info[0].size = rom_len[0]; - info[0].meta = NULL; // Not relevant at this moment. - - for (i = 1; i < roms; i++) - { - if (rom_paths[i] && - !g_extern.system.info.need_fullpath && - (rom_len[i] = read_file(rom_paths[i], &rom_buf[i])) == -1) + if (!need_fullpath && *path) // Load the ROM into memory. { - RARCH_ERR("Could not read ROM file: \"%s\".\n", rom_paths[i]); - ret = false; - goto end; + RARCH_LOG("Loading ROM file: %s.\n", path); + // First ROM is significant, attempt to do patching, CRC checking, etc ... + long size = i == 0 ? read_rom_file(path, (void**)&info[i].data) : read_file(path, (void**)&info[i].data); + if (size < 0) + { + RARCH_ERR("Could not read ROM file \"%s\".\n", path); + ret = false; + goto end; + } + + info[i].size = size; } - - info[i].path = rom_paths[i]; - info[i].data = rom_buf[i]; - info[i].size = rom_len[i]; - info[i].meta = NULL; + else + RARCH_LOG("ROM loading skipped. Implementation will load it on its own.\n"); } - if (rom_type == 0) - ret = pretro_load_game(&info[0]); + if (special) + ret = pretro_load_game_special(special->id, info, roms->size); else - ret = pretro_load_game_special(rom_type, info, roms); + ret = pretro_load_game(info); if (!ret) RARCH_ERR("Failed to load game.\n"); end: - for (i = 0; i < MAX_ROMS; i++) - free(rom_buf[i]); + for (i = 0; i < roms->size; i++) + free((void*)info[i].data); + free(info); return ret; } -static bool load_normal_rom(void) +bool init_rom_file(void) { - if (g_extern.libretro_no_rom && g_extern.system.no_game) - return pretro_load_game(NULL); - else if (g_extern.libretro_no_rom && !g_extern.system.no_game) - { - RARCH_ERR("No ROM is used, but libretro core does not support this.\n"); + unsigned i; + + g_extern.temporary_roms = string_list_new(); + if (!g_extern.temporary_roms) return false; + + const struct retro_game_special_info *special = NULL; + + if (*g_extern.subsystem) + { + special = libretro_find_subsystem_info(g_extern.system.special, g_extern.system.num_special, + g_extern.subsystem); + + if (!special) + { + RARCH_ERR("Failed to find subsystem \"%s\" in libretro implementation.\n", + g_extern.subsystem); + return false; + } + + if (special->num_roms && !g_extern.subsystem_fullpaths) + { + RARCH_ERR("libretro core requires special ROMs, but none were provided.\n"); + return false; + } + else if (special->num_roms && special->num_roms != g_extern.subsystem_fullpaths->size) + { + RARCH_ERR("libretro core requires %u ROMs for subsystem \"%s\", but %u ROMs were provided.\n", special->num_roms, special->desc, + (unsigned)g_extern.subsystem_fullpaths->size); + return false; + } + else if (!special->num_roms && g_extern.subsystem_fullpaths && g_extern.subsystem_fullpaths->size) + { + RARCH_ERR("libretro core takes no ROMs for subsystem \"%s\", but %u ROMs were provided.\n", special->desc, + (unsigned)g_extern.subsystem_fullpaths->size); + return false; + } + } + + union string_list_elem_attr attr; + struct string_list *roms = string_list_new(); + if (!roms) + return false; + + if (*g_extern.subsystem) + { + for (i = 0; i < g_extern.subsystem_fullpaths->size; i++) + { + attr.i = special->roms[i].block_extract; + attr.i |= special->roms[i].need_fullpath << 1; + attr.i |= special->roms[i].required << 2; + string_list_append(roms, g_extern.subsystem_fullpaths->elems[i].data, attr); + } } else { - const char *path = g_extern.fullpath; - return load_roms(0, &path, 1); + attr.i = g_extern.system.info.block_extract; + attr.i |= g_extern.system.info.need_fullpath << 1; + attr.i |= (!g_extern.system.no_game) << 2; + string_list_append(roms, g_extern.fullpath, attr); } -} -static bool load_sgb_rom(void) -{ - const char *path[2] = { - *g_extern.fullpath ? g_extern.fullpath : NULL, - g_extern.gb_rom_path - }; - - return load_roms(RETRO_GAME_TYPE_SUPER_GAME_BOY, path, 2); -} - -static bool load_bsx_rom(bool slotted) -{ - const char *path[2] = { - *g_extern.fullpath ? g_extern.fullpath : NULL, - g_extern.bsx_rom_path - }; - - return load_roms(slotted ? RETRO_GAME_TYPE_BSX_SLOTTED : RETRO_GAME_TYPE_BSX, path, 2); -} - -static bool load_sufami_rom(void) -{ - const char *path[3] = { - *g_extern.fullpath ? g_extern.fullpath : NULL, - *g_extern.sufami_rom_path[0] ? g_extern.sufami_rom_path[0] : NULL, - *g_extern.sufami_rom_path[1] ? g_extern.sufami_rom_path[1] : NULL, - }; - - return load_roms(RETRO_GAME_TYPE_SUFAMI_TURBO, path, 3); -} - -bool init_rom_file(enum rarch_game_type type) -{ #ifdef HAVE_ZLIB - if (*g_extern.fullpath && !g_extern.system.block_extract) + // Try to extract every ROM we're going to load if appropriate. + for (i = 0; i < roms->size; i++) { - const char *ext = path_get_extension(g_extern.fullpath); + // block extract check + if (roms->elems[i].attr.i & 1) + continue; + + const char *ext = path_get_extension(roms->elems[i].data); + + const char *valid_ext = special ? + special->roms[i].valid_extensions : + g_extern.system.info.valid_extensions; + if (ext && !strcasecmp(ext, "zip")) { - g_extern.rom_file_temporary = true; - - if (!zlib_extract_first_rom(g_extern.fullpath, sizeof(g_extern.fullpath), g_extern.system.valid_extensions)) + char temporary_rom[PATH_MAX]; + strlcpy(temporary_rom, roms->elems[i].data, sizeof(temporary_rom)); + if (!zlib_extract_first_rom(temporary_rom, sizeof(temporary_rom), valid_ext)) { - RARCH_ERR("Failed to extract ROM from zipped file: %s.\n", g_extern.fullpath); - g_extern.rom_file_temporary = false; + RARCH_ERR("Failed to extract ROM from zipped file: %s.\n", temporary_rom); + string_list_free(roms); return false; } - strlcpy(g_extern.last_rom, g_extern.fullpath, sizeof(g_extern.last_rom)); + string_list_append(g_extern.temporary_roms, temporary_rom, attr); } } #endif - switch (type) - { - case RARCH_CART_SGB: - if (!load_sgb_rom()) - return false; - break; - - case RARCH_CART_NORMAL: - if (!load_normal_rom()) - return false; - break; - - case RARCH_CART_BSX: - if (!load_bsx_rom(false)) - return false; - break; - - case RARCH_CART_BSX_SLOTTED: - if (!load_bsx_rom(true)) - return false; - break; - - case RARCH_CART_SUFAMI: - if (!load_sufami_rom()) - return false; - break; - - default: - RARCH_ERR("Invalid ROM type.\n"); - return false; - } - - return true; + // Set attr to need_fullpath as appropriate. + + bool ret = load_roms(special, roms); + string_list_free(roms); + return ret; } diff --git a/file.h b/file.h index 58dea5e60e..c1647e20f4 100644 --- a/file.h +++ b/file.h @@ -36,7 +36,7 @@ bool save_state(const char *path); void load_ram_file(const char *path, int type); void save_ram_file(const char *path, int type); -bool init_rom_file(enum rarch_game_type type); +bool init_rom_file(void); #ifdef __cplusplus } diff --git a/frontend/menu/menu_common.c b/frontend/menu/menu_common.c index 334892b498..a318649ea2 100644 --- a/frontend/menu/menu_common.c +++ b/frontend/menu/menu_common.c @@ -244,12 +244,7 @@ void menu_rom_history_push_current(void) char tmp[PATH_MAX]; - // We loaded a zip, and fullpath points to the extracted file. - // Look at basename instead. - if (g_extern.rom_file_temporary) - snprintf(tmp, sizeof(tmp), "%s.zip", g_extern.basename); - else - strlcpy(tmp, g_extern.fullpath, sizeof(tmp)); + strlcpy(tmp, g_extern.fullpath, sizeof(tmp)); if (*tmp) path_resolve_realpath(tmp, sizeof(tmp)); diff --git a/general.h b/general.h index 8d3d2c898b..87e592bb51 100644 --- a/general.h +++ b/general.h @@ -316,15 +316,6 @@ struct settings bool core_specific_config; }; -enum rarch_game_type -{ - RARCH_CART_NORMAL = 0, - RARCH_CART_SGB, - RARCH_CART_BSX, - RARCH_CART_BSX_SLOTTED, - RARCH_CART_SUFAMI -}; - typedef struct rarch_resolution { unsigned idx; @@ -358,9 +349,8 @@ struct global #endif bool force_fullscreen; - bool rom_file_temporary; - char last_rom[PATH_MAX]; - enum rarch_game_type game_type; + struct string_list *temporary_roms; + uint32_t cart_crc; char gb_rom_path[PATH_MAX]; @@ -386,11 +376,15 @@ struct global char basename[PATH_MAX]; char fullpath[PATH_MAX]; - char savefile_name_srm[PATH_MAX]; - char savefile_name_rtc[PATH_MAX]; // Make sure that fill_pathname has space. - char savefile_name_psrm[PATH_MAX]; - char savefile_name_asrm[PATH_MAX]; - char savefile_name_bsrm[PATH_MAX]; + + // A list of save types and associated paths for all ROMs. + struct string_list *savefiles; + + // For --subsystem ROMs. + char subsystem[256]; + struct string_list *subsystem_fullpaths; + + char savefile_name[PATH_MAX]; char savestate_name[PATH_MAX]; // Used on reentrancy to use a savestate dir. @@ -448,6 +442,9 @@ struct global retro_usec_t frame_time_last; core_option_manager_t *core_options; + + struct retro_game_special_info *special; + unsigned num_special; } system; struct @@ -563,7 +560,8 @@ struct global unsigned turbo_count; // Autosave support. - autosave_t *autosave[2]; + autosave_t **autosave; + unsigned num_autosave; // Netplay. #ifdef HAVE_NETPLAY diff --git a/libretro.h b/libretro.h index 6d8a711598..82cc825772 100755 --- a/libretro.h +++ b/libretro.h @@ -162,7 +162,7 @@ extern "C" { // Regular save ram. This ram is usually found on a game cartridge, backed up by a battery. // If save game data is too complex for a single memory buffer, -// the SYSTEM_DIRECTORY environment callback can be used. +// the SAVE_DIRECTORY (preferably) or SYSTEM_DIRECTORY environment callback can be used. #define RETRO_MEMORY_SAVE_RAM 0 // Some games have a built-in clock to keep track of time. @@ -175,21 +175,6 @@ extern "C" { // Video ram lets a frontend peek into a game systems video RAM (VRAM). #define RETRO_MEMORY_VIDEO_RAM 3 -// Special memory types. -#define RETRO_MEMORY_SNES_BSX_RAM ((1 << 8) | RETRO_MEMORY_SAVE_RAM) -#define RETRO_MEMORY_SNES_BSX_PRAM ((2 << 8) | RETRO_MEMORY_SAVE_RAM) -#define RETRO_MEMORY_SNES_SUFAMI_TURBO_A_RAM ((3 << 8) | RETRO_MEMORY_SAVE_RAM) -#define RETRO_MEMORY_SNES_SUFAMI_TURBO_B_RAM ((4 << 8) | RETRO_MEMORY_SAVE_RAM) -#define RETRO_MEMORY_SNES_GAME_BOY_RAM ((5 << 8) | RETRO_MEMORY_SAVE_RAM) -#define RETRO_MEMORY_SNES_GAME_BOY_RTC ((6 << 8) | RETRO_MEMORY_RTC) - -// Special game types passed into retro_load_game_special(). -// Only used when multiple ROMs are required. -#define RETRO_GAME_TYPE_BSX 0x101 -#define RETRO_GAME_TYPE_BSX_SLOTTED 0x102 -#define RETRO_GAME_TYPE_SUFAMI_TURBO 0x103 -#define RETRO_GAME_TYPE_SUPER_GAME_BOY 0x104 - // Keysyms used for ID in input state callback when polling RETRO_KEYBOARD. enum retro_key { @@ -614,9 +599,59 @@ enum retro_mod // e.g. for cases where the frontend wants to call directly into the core. // // If a core wants to expose this interface, SET_PROC_ADDRESS_CALLBACK **MUST** be called from within retro_set_environment(). + // +#define RETRO_ENVIRONMENT_SET_SPECIAL_GAME_TYPES 34 + // const struct retro_game_special_info * -- + // This environment call introduces the concept of libretro "subsystems". + // A subsystem is a variant of a libretro core which supports different kinds of games. + // The purpose of this is to support e.g. emulators which might have special needs, e.g. Super Nintendos Super GameBoy, Sufami Turbo. + // It can also be used to pick among subsystems in an explicit way if the libretro implementation is a multi-system emulator itself. + // + // Loading a game via a subsystem is done with retro_load_game_special(), + // and this environment call allows a libretro core to expose which subsystems are supported for use with retro_load_game_special(). + // A core passes an array of retro_game_special_info which is terminated with a zeroed out retro_game_special_info struct. + // + // If a core wants to use this functionality, SET_SPECIAL_GAME_TYPES **MUST** be called from within retro_set_environment(). + +struct retro_game_special_memory_info +{ + const char *extension; // The extension associated with a memory type, e.g. "psram". + unsigned type; // The memory type for retro_get_memory(). This should be at least 0x100 to avoid conflict with standardized libretro memory types. +}; + +struct retro_game_special_rom_info +{ + const char *desc; // Describes what the ROM is (SGB bios, GB rom, etc). + const char *ident; // A computer friendly short string identifier for the ROM type. Must be [a-z]. + const char *valid_extensions; // Same definition as retro_get_system_info(). + bool need_fullpath; // Same definition as retro_get_system_info(). + bool block_extract; // Same definition as retro_get_system_info(). + bool required; // This is set if the ROM is required to load a game. If this is set to false, a zeroed-out retro_game_info can be passed. + + // ROMs can have multiple associated persistent memory types (retro_get_memory()). + const struct retro_game_special_memory_info *memory; + unsigned num_memory; +}; + +struct retro_game_special_info +{ + const char *desc; // Human-readable string of the subsystem type, e.g. "Super GameBoy" + // A computer friendly short string identifier for the subsystem type. + // This name must be [a-z]. + // E.g. if desc is "Super GameBoy", this can be "sgb". + // This identifier can be used for command-line interfaces, etc. + const char *ident; + + // Infos for each ROM. The first entry is assumed to be the "most significant" ROM for frontend purposes. + // E.g. with Super GameBoy, the first ROM should be the GameBoy ROM, as it is the most "significant" ROM to a user. + // If a frontend creates new file paths based on the ROM used (e.g. savestates), it should use the path for the first ROM to do so. + const struct retro_game_special_rom_info *roms; + + unsigned num_roms; // Number of ROMs associated with a subsystem. + unsigned id; // The type passed to retro_load_game_special(). +}; typedef void (*retro_proc_address_t)(void); - // libretro API extension functions: // (None here so far). ////// diff --git a/retroarch.c b/retroarch.c index 756ba61b97..ccf5b90947 100644 --- a/retroarch.c +++ b/retroarch.c @@ -836,19 +836,53 @@ static void set_basename(const char *path) *dst = '\0'; } +static void set_special_paths(char **argv, unsigned roms) +{ + unsigned i; + + // First ROM is the significant one. + set_basename(argv[0]); + + g_extern.subsystem_fullpaths = string_list_new(); + rarch_assert(g_extern.subsystem_fullpaths); + + union string_list_elem_attr attr; + attr.i = 0; + + for (i = 0; i < roms; i++) + string_list_append(g_extern.subsystem_fullpaths, argv[i], attr); + + // We defer SRAM path updates until we can resolve it. + // It is more complicated for special game types. + + if (!g_extern.has_set_state_path) + fill_pathname_noext(g_extern.savestate_name, g_extern.basename, ".state", sizeof(g_extern.savestate_name)); + + if (path_is_directory(g_extern.savestate_name)) + { + fill_pathname_dir(g_extern.savestate_name, g_extern.basename, ".state", sizeof(g_extern.savestate_name)); + RARCH_LOG("Redirecting save state to \"%s\".\n", g_extern.savestate_name); + } + + // If this is already set, + // do not overwrite it as this was initialized before in a menu or otherwise. + if (!*g_settings.system_directory) + fill_pathname_basedir(g_settings.system_directory, argv[0], sizeof(g_settings.system_directory)); +} + static void set_paths(const char *path) { set_basename(path); if (!g_extern.has_set_save_path) - fill_pathname_noext(g_extern.savefile_name_srm, g_extern.basename, ".srm", sizeof(g_extern.savefile_name_srm)); + fill_pathname_noext(g_extern.savefile_name, g_extern.basename, ".srm", sizeof(g_extern.savefile_name)); if (!g_extern.has_set_state_path) fill_pathname_noext(g_extern.savestate_name, g_extern.basename, ".state", sizeof(g_extern.savestate_name)); - if (path_is_directory(g_extern.savefile_name_srm)) + if (path_is_directory(g_extern.savefile_name)) { - fill_pathname_dir(g_extern.savefile_name_srm, g_extern.basename, ".srm", sizeof(g_extern.savefile_name_srm)); - RARCH_LOG("Redirecting save file to \"%s\".\n", g_extern.savefile_name_srm); + fill_pathname_dir(g_extern.savefile_name, g_extern.basename, ".srm", sizeof(g_extern.savefile_name)); + RARCH_LOG("Redirecting save file to \"%s\".\n", g_extern.savefile_name); } if (path_is_directory(g_extern.savestate_name)) { @@ -895,7 +929,6 @@ static void parse_input(int argc, char *argv[]) { "size", 1, &val, 's' }, #endif { "verbose", 0, NULL, 'v' }, - { "gameboy", 1, NULL, 'g' }, { "config", 1, NULL, 'c' }, { "appendconfig", 1, &val, 'C' }, { "mouse", 1, NULL, 'm' }, @@ -905,11 +938,7 @@ static void parse_input(int argc, char *argv[]) { "justifiers", 0, NULL, 'J' }, { "dualanalog", 1, NULL, 'A' }, { "savestate", 1, NULL, 'S' }, - { "bsx", 1, NULL, 'b' }, - { "bsxslot", 1, NULL, 'B' }, { "multitap", 0, NULL, '4' }, - { "sufamiA", 1, NULL, 'Y' }, - { "sufamiB", 1, NULL, 'Z' }, #ifdef HAVE_BSV_MOVIE { "bsvplay", 1, NULL, 'P' }, { "bsvrecord", 1, NULL, 'R' }, @@ -932,6 +961,7 @@ static void parse_input(int argc, char *argv[]) { "no-patch", 0, &val, 'n' }, { "detach", 0, NULL, 'D' }, { "features", 0, &val, 'f' }, + { "subsystem", 1, NULL, 'Z' }, { NULL, 0, NULL, 0 } }; @@ -959,7 +989,7 @@ static void parse_input(int argc, char *argv[]) #define BSV_MOVIE_ARG #endif - const char *optstring = "hs:fvS:m:p4jJA:g:b:c:B:Y:Z:U:DN:" BSV_MOVIE_ARG NETPLAY_ARG DYNAMIC_ARG FFMPEG_RECORD_ARG; + const char *optstring = "hs:fvS:m:p4jJA:c:U:DN:" BSV_MOVIE_ARG NETPLAY_ARG DYNAMIC_ARG FFMPEG_RECORD_ARG; for (;;) { @@ -970,7 +1000,6 @@ static void parse_input(int argc, char *argv[]) if (c == -1) break; - switch (c) { case 'h': @@ -992,6 +1021,10 @@ static void parse_input(int argc, char *argv[]) g_extern.has_set_libretro_device[1] = true; break; + case 'Z': + strlcpy(g_extern.subsystem, optarg, sizeof(g_extern.subsystem)); + break; + case 'A': port = strtol(optarg, NULL, 0); if (port < 1 || port > MAX_PLAYERS) @@ -1005,7 +1038,7 @@ static void parse_input(int argc, char *argv[]) break; case 's': - strlcpy(g_extern.savefile_name_srm, optarg, sizeof(g_extern.savefile_name_srm)); + strlcpy(g_extern.savefile_name, optarg, sizeof(g_extern.savefile_name)); g_extern.has_set_save_path = true; break; @@ -1013,31 +1046,6 @@ static void parse_input(int argc, char *argv[]) g_extern.force_fullscreen = true; break; - case 'g': - strlcpy(g_extern.gb_rom_path, optarg, sizeof(g_extern.gb_rom_path)); - g_extern.game_type = RARCH_CART_SGB; - break; - - case 'b': - strlcpy(g_extern.bsx_rom_path, optarg, sizeof(g_extern.bsx_rom_path)); - g_extern.game_type = RARCH_CART_BSX; - break; - - case 'B': - strlcpy(g_extern.bsx_rom_path, optarg, sizeof(g_extern.bsx_rom_path)); - g_extern.game_type = RARCH_CART_BSX_SLOTTED; - break; - - case 'Y': - strlcpy(g_extern.sufami_rom_path[0], optarg, sizeof(g_extern.sufami_rom_path[0])); - g_extern.game_type = RARCH_CART_SUFAMI; - break; - - case 'Z': - strlcpy(g_extern.sufami_rom_path[1], optarg, sizeof(g_extern.sufami_rom_path[1])); - g_extern.game_type = RARCH_CART_SUFAMI; - break; - case 'S': strlcpy(g_extern.savestate_name, optarg, sizeof(g_extern.savestate_name)); g_extern.has_set_state_path = true; @@ -1239,14 +1247,16 @@ static void parse_input(int argc, char *argv[]) rarch_fail(1, "parse_input()"); } } - else if (optind < argc) + else if (!*g_extern.subsystem && optind < argc) set_paths(argv[optind]); + else if (*g_extern.subsystem && optind < argc) + set_special_paths(argv + optind, argc - optind); else g_extern.libretro_no_rom = true; // Copy SRM/state dirs used, so they can be reused on reentrancy. - if (g_extern.has_set_save_path && path_is_directory(g_extern.savefile_name_srm)) - strlcpy(g_extern.savefile_dir, g_extern.savefile_name_srm, sizeof(g_extern.savefile_dir)); + if (g_extern.has_set_save_path && path_is_directory(g_extern.savefile_name)) + strlcpy(g_extern.savefile_dir, g_extern.savefile_name, sizeof(g_extern.savefile_dir)); if (g_extern.has_set_state_path && path_is_directory(g_extern.savestate_name)) strlcpy(g_extern.savestate_dir, g_extern.savestate_name, sizeof(g_extern.savestate_dir)); } @@ -1302,73 +1312,26 @@ static void init_controllers(void) static inline void load_save_files(void) { - switch (g_extern.game_type) - { - case RARCH_CART_NORMAL: - load_ram_file(g_extern.savefile_name_srm, RETRO_MEMORY_SAVE_RAM); - load_ram_file(g_extern.savefile_name_rtc, RETRO_MEMORY_RTC); - break; + unsigned i; + if (!g_extern.savefiles) + return; - case RARCH_CART_SGB: - load_ram_file(g_extern.savefile_name_srm, RETRO_MEMORY_SNES_GAME_BOY_RAM); - load_ram_file(g_extern.savefile_name_rtc, RETRO_MEMORY_SNES_GAME_BOY_RTC); - break; - - case RARCH_CART_BSX: - case RARCH_CART_BSX_SLOTTED: - load_ram_file(g_extern.savefile_name_srm, RETRO_MEMORY_SNES_BSX_RAM); - load_ram_file(g_extern.savefile_name_psrm, RETRO_MEMORY_SNES_BSX_PRAM); - break; - - case RARCH_CART_SUFAMI: - load_ram_file(g_extern.savefile_name_asrm, RETRO_MEMORY_SNES_SUFAMI_TURBO_A_RAM); - load_ram_file(g_extern.savefile_name_bsrm, RETRO_MEMORY_SNES_SUFAMI_TURBO_B_RAM); - break; - - default: - break; - } + for (i = 0; i < g_extern.savefiles->size; i++) + load_ram_file(g_extern.savefiles->elems[i].data, g_extern.savefiles->elems[i].attr.i); } static inline void save_files(void) { - switch (g_extern.game_type) + unsigned i; + if (!g_extern.savefiles) + return; + + for (i = 0; i < g_extern.savefiles->size; i++) { - case RARCH_CART_NORMAL: - RARCH_LOG("Saving regular SRAM.\n"); - RARCH_LOG("SRM: %s\n", g_extern.savefile_name_srm); - RARCH_LOG("RTC: %s\n", g_extern.savefile_name_rtc); - save_ram_file(g_extern.savefile_name_srm, RETRO_MEMORY_SAVE_RAM); - save_ram_file(g_extern.savefile_name_rtc, RETRO_MEMORY_RTC); - break; - - case RARCH_CART_SGB: - RARCH_LOG("Saving Gameboy SRAM.\n"); - RARCH_LOG("SRM: %s\n", g_extern.savefile_name_srm); - RARCH_LOG("RTC: %s\n", g_extern.savefile_name_rtc); - save_ram_file(g_extern.savefile_name_srm, RETRO_MEMORY_SNES_GAME_BOY_RAM); - save_ram_file(g_extern.savefile_name_rtc, RETRO_MEMORY_SNES_GAME_BOY_RTC); - break; - - case RARCH_CART_BSX: - case RARCH_CART_BSX_SLOTTED: - RARCH_LOG("Saving BSX (P)RAM.\n"); - RARCH_LOG("SRM: %s\n", g_extern.savefile_name_srm); - RARCH_LOG("PSRM: %s\n", g_extern.savefile_name_psrm); - save_ram_file(g_extern.savefile_name_srm, RETRO_MEMORY_SNES_BSX_RAM); - save_ram_file(g_extern.savefile_name_psrm, RETRO_MEMORY_SNES_BSX_PRAM); - break; - - case RARCH_CART_SUFAMI: - RARCH_LOG("Saving Sufami turbo A/B RAM.\n"); - RARCH_LOG("ASRM: %s\n", g_extern.savefile_name_asrm); - RARCH_LOG("BSRM: %s\n", g_extern.savefile_name_bsrm); - save_ram_file(g_extern.savefile_name_asrm, RETRO_MEMORY_SNES_SUFAMI_TURBO_A_RAM); - save_ram_file(g_extern.savefile_name_bsrm, RETRO_MEMORY_SNES_SUFAMI_TURBO_B_RAM); - break; - - default: - break; + unsigned type = g_extern.savefiles->elems[i].attr.i; + const char *path = g_extern.savefiles->elems[i].data; + RARCH_LOG("Saving RAM type #%u to \"%s\".\n", type, path); + save_ram_file(path, type); } } @@ -1727,54 +1690,29 @@ static void init_libretro_cbs(void) #if defined(HAVE_THREADS) void rarch_init_autosave(void) { - int ram_types[2] = {-1, -1}; - const char *ram_paths[2] = {NULL, NULL}; + if (g_settings.autosave_interval < 1 || !g_extern.savefiles) + return; - switch (g_extern.game_type) + g_extern.autosave = (autosave_t**)calloc(g_extern.savefiles->size, sizeof(*g_extern.autosave)); + if (!g_extern.autosave) + return; + + g_extern.num_autosave = g_extern.savefiles->size; + + unsigned i; + for (i = 0; i < g_extern.savefiles->size; i++) { - case RARCH_CART_BSX: - case RARCH_CART_BSX_SLOTTED: - ram_types[0] = RETRO_MEMORY_SNES_BSX_RAM; - ram_types[1] = RETRO_MEMORY_SNES_BSX_PRAM; - ram_paths[0] = g_extern.savefile_name_srm; - ram_paths[1] = g_extern.savefile_name_psrm; - break; + const char *path = g_extern.savefiles->elems[i].data; + unsigned type = g_extern.savefiles->elems[i].attr.i; - case RARCH_CART_SUFAMI: - ram_types[0] = RETRO_MEMORY_SNES_SUFAMI_TURBO_A_RAM; - ram_types[1] = RETRO_MEMORY_SNES_SUFAMI_TURBO_B_RAM; - ram_paths[0] = g_extern.savefile_name_asrm; - ram_paths[1] = g_extern.savefile_name_bsrm; - break; - - case RARCH_CART_SGB: - ram_types[0] = RETRO_MEMORY_SNES_GAME_BOY_RAM; - ram_types[1] = RETRO_MEMORY_SNES_GAME_BOY_RTC; - ram_paths[0] = g_extern.savefile_name_srm; - ram_paths[1] = g_extern.savefile_name_rtc; - break; - - default: - ram_types[0] = RETRO_MEMORY_SAVE_RAM; - ram_types[1] = RETRO_MEMORY_RTC; - ram_paths[0] = g_extern.savefile_name_srm; - ram_paths[1] = g_extern.savefile_name_rtc; - } - - if (g_settings.autosave_interval > 0) - { - unsigned i; - for (i = 0; i < sizeof(g_extern.autosave) / sizeof(g_extern.autosave[0]); i++) + if (pretro_get_memory_size(type) > 0) { - if (ram_paths[i] && *ram_paths[i] && pretro_get_memory_size(ram_types[i]) > 0) - { - g_extern.autosave[i] = autosave_new(ram_paths[i], - pretro_get_memory_data(ram_types[i]), - pretro_get_memory_size(ram_types[i]), - g_settings.autosave_interval); - if (!g_extern.autosave[i]) - RARCH_WARN("Could not initialize autosave.\n"); - } + g_extern.autosave[i] = autosave_new(path, + pretro_get_memory_data(type), + pretro_get_memory_size(type), + g_settings.autosave_interval); + if (!g_extern.autosave[i]) + RARCH_WARN("Could not initialize autosave.\n"); } } } @@ -1782,12 +1720,10 @@ void rarch_init_autosave(void) void rarch_deinit_autosave(void) { unsigned i; - for (i = 0; i < ARRAY_SIZE(g_extern.autosave); i++) - { - if (g_extern.autosave[i]) - autosave_free(g_extern.autosave[i]); - g_extern.autosave[i] = NULL; - } + for (i = 0; i < g_extern.num_autosave; i++) + autosave_free(g_extern.autosave[i]); + free(g_extern.autosave); + g_extern.num_autosave = 0; } #endif @@ -1838,84 +1774,85 @@ static void set_savestate_auto_index(void) static void fill_pathnames(void) { - switch (g_extern.game_type) + string_list_free(g_extern.savefiles); + g_extern.savefiles = string_list_new(); + rarch_assert(g_extern.savefiles); + + // For subsystems, we know exactly which RAM types are supported. + if (*g_extern.subsystem) { - case RARCH_CART_BSX: - case RARCH_CART_BSX_SLOTTED: - // BSX PSRM - if (!g_extern.has_set_save_path) + unsigned i; + const struct retro_game_special_info *info = libretro_find_subsystem_info(g_extern.system.special, g_extern.system.num_special, g_extern.subsystem); + + // We'll handle this error gracefully later. + unsigned num_roms = info ? info->num_roms : 0; + bool use_sram_dir = path_is_directory(g_extern.savefile_name); + + for (i = 0; i < num_roms; i++) + { + unsigned j; + for (j = 0; j < info->roms[i].num_memory; j++) { - fill_pathname(g_extern.savefile_name_srm, - g_extern.bsx_rom_path, ".srm", sizeof(g_extern.savefile_name_srm)); + const struct retro_game_special_memory_info *mem = &info->roms[i].memory[j]; + union string_list_elem_attr attr; + + char path[PATH_MAX]; + char ext[32]; + + snprintf(ext, sizeof(ext), ".%s", mem->extension); + + if (use_sram_dir) + { + // Redirect ROM fullpath to save directory. + strlcpy(path, g_extern.savefile_name, sizeof(path)); + fill_pathname_dir(path, g_extern.subsystem_fullpaths->elems[i].data, ext, + sizeof(path)); + } + else + { + fill_pathname(path, g_extern.subsystem_fullpaths->elems[i].data, + ext, sizeof(path)); + } + + attr.i = mem->type; + string_list_append(g_extern.savefiles, path, attr); } + } - fill_pathname(g_extern.savefile_name_psrm, - g_extern.savefile_name_srm, ".psrm", sizeof(g_extern.savefile_name_psrm)); + // Let other relevant paths be inferred from the main SRAM location. + if (!g_extern.has_set_save_path) + fill_pathname_noext(g_extern.savefile_name, g_extern.basename, ".srm", sizeof(g_extern.savefile_name)); + if (path_is_directory(g_extern.savefile_name)) + { + fill_pathname_dir(g_extern.savefile_name, g_extern.basename, ".srm", sizeof(g_extern.savefile_name)); + RARCH_LOG("Redirecting save file to \"%s\".\n", g_extern.savefile_name); + } + } + else + { + union string_list_elem_attr attr; + attr.i = RETRO_MEMORY_SAVE_RAM; + string_list_append(g_extern.savefiles, g_extern.savefile_name, attr); - if (!g_extern.has_set_state_path) - { - fill_pathname(g_extern.savestate_name, - g_extern.bsx_rom_path, ".state", sizeof(g_extern.savestate_name)); - } - break; - - case RARCH_CART_SUFAMI: - if (g_extern.has_set_save_path && *g_extern.sufami_rom_path[0] && *g_extern.sufami_rom_path[1]) - RARCH_WARN("Sufami Turbo SRAM paths will be inferred from their respective paths to avoid conflicts.\n"); - - // SUFAMI ARAM - fill_pathname(g_extern.savefile_name_asrm, - g_extern.sufami_rom_path[0], ".srm", sizeof(g_extern.savefile_name_asrm)); - - // SUFAMI BRAM - fill_pathname(g_extern.savefile_name_bsrm, - g_extern.sufami_rom_path[1], ".srm", sizeof(g_extern.savefile_name_bsrm)); - - if (!g_extern.has_set_state_path) - { - fill_pathname(g_extern.savestate_name, - *g_extern.sufami_rom_path[0] ? - g_extern.sufami_rom_path[0] : g_extern.sufami_rom_path[1], - ".state", sizeof(g_extern.savestate_name)); - } - break; - - case RARCH_CART_SGB: - if (!g_extern.has_set_save_path) - { - fill_pathname(g_extern.savefile_name_srm, - g_extern.gb_rom_path, ".srm", sizeof(g_extern.savefile_name_srm)); - } - - if (!g_extern.has_set_state_path) - { - fill_pathname(g_extern.savestate_name, - g_extern.gb_rom_path, ".state", sizeof(g_extern.savestate_name)); - } - - fill_pathname(g_extern.savefile_name_rtc, - g_extern.savefile_name_srm, ".rtc", sizeof(g_extern.savefile_name_rtc)); - break; - - default: - // Infer .rtc save path from save ram path. - fill_pathname(g_extern.savefile_name_rtc, - g_extern.savefile_name_srm, ".rtc", sizeof(g_extern.savefile_name_rtc)); + // Infer .rtc save path from save ram path. + char savefile_name_rtc[PATH_MAX]; + attr.i = RETRO_MEMORY_RTC; + fill_pathname(savefile_name_rtc, + g_extern.savefile_name, ".rtc", sizeof(savefile_name_rtc)); + string_list_append(g_extern.savefiles, savefile_name_rtc, attr); } #ifdef HAVE_BSV_MOVIE - fill_pathname(g_extern.bsv.movie_path, g_extern.savefile_name_srm, "", sizeof(g_extern.bsv.movie_path)); + fill_pathname(g_extern.bsv.movie_path, g_extern.savefile_name, "", sizeof(g_extern.bsv.movie_path)); #endif if (*g_extern.basename) { - if (!(*g_extern.ups_name)) + if (!*g_extern.ups_name) fill_pathname_noext(g_extern.ups_name, g_extern.basename, ".ups", sizeof(g_extern.ups_name)); - - if (!(*g_extern.bps_name)) + if (!*g_extern.bps_name) fill_pathname_noext(g_extern.bps_name, g_extern.basename, ".bps", sizeof(g_extern.bps_name)); - - if (!(*g_extern.ips_name)) + if (!*g_extern.ips_name) fill_pathname_noext(g_extern.ips_name, g_extern.basename, ".ips", sizeof(g_extern.ips_name)); } } @@ -2530,19 +2467,22 @@ void rarch_disk_control_append_image(const char *path) rarch_deinit_autosave(); #endif - // Update paths for our new image. - // If we actually use append_image, - // we assume that we started out in a single disk case, - // and that this way of doing it makes the most sense. - set_paths(path); - fill_pathnames(); + // TODO: Need to figure out what to do with subsystems case. + if (!*g_extern.subsystem) + { + // Update paths for our new image. + // If we actually use append_image, + // we assume that we started out in a single disk case, + // and that this way of doing it makes the most sense. + set_paths(path); + fill_pathnames(); + } #if defined(HAVE_THREADS) rarch_init_autosave(); #endif rarch_disk_control_set_eject(false, false); - } void rarch_disk_control_set_eject(bool new_state, bool log) @@ -2874,7 +2814,6 @@ static void init_state(void) { g_extern.video_active = true; g_extern.audio_active = true; - g_extern.game_type = RARCH_CART_NORMAL; } static void init_state_first(void) @@ -3007,19 +2946,18 @@ int rarch_main_init(int argc, char *argv[]) if (g_extern.libretro_no_rom && !g_extern.libretro_dummy) { - if (!init_rom_file(g_extern.game_type)) + if (!init_rom_file()) goto error; } else if (!g_extern.libretro_dummy) { fill_pathnames(); - if (!init_rom_file(g_extern.game_type)) + if (!init_rom_file()) goto error; set_savestate_auto_index(); - if (!g_extern.sram_load_disable) load_save_files(); else @@ -3278,13 +3216,24 @@ void rarch_main_deinit(void) pretro_deinit(); uninit_libretro_sym(); - if (g_extern.rom_file_temporary) + if (g_extern.temporary_roms) { - RARCH_LOG("Removing temporary ROM file: %s.\n", g_extern.last_rom); - if (remove(g_extern.last_rom) < 0) - RARCH_ERR("Failed to remove temporary file: %s.\n", g_extern.last_rom); - g_extern.rom_file_temporary = false; + unsigned i; + for (i = 0; i < g_extern.temporary_roms->size; i++) + { + const char *path = g_extern.temporary_roms->elems[i].data; + RARCH_LOG("Removing temporary ROM file: %s.\n", path); + if (remove(path) < 0) + RARCH_ERR("Failed to remove temporary file: %s.\n", path); + } } + string_list_free(g_extern.temporary_roms); + g_extern.temporary_roms = NULL; + + string_list_free(g_extern.subsystem_fullpaths); + string_list_free(g_extern.savefiles); + g_extern.subsystem_fullpaths = NULL; + g_extern.savefiles = NULL; g_extern.main_is_init = false; } diff --git a/settings.c b/settings.c index 526bd79ff1..c0f57ca986 100644 --- a/settings.c +++ b/settings.c @@ -1009,8 +1009,8 @@ bool config_load_file(const char *path, bool set_defaults) else if (path_is_directory(tmp_str)) { strlcpy(g_extern.savefile_dir, tmp_str, sizeof(g_extern.savefile_dir)); - strlcpy(g_extern.savefile_name_srm, tmp_str, sizeof(g_extern.savefile_name_srm)); - fill_pathname_dir(g_extern.savefile_name_srm, g_extern.basename, ".srm", sizeof(g_extern.savefile_name_srm)); + strlcpy(g_extern.savefile_name, tmp_str, sizeof(g_extern.savefile_name)); + fill_pathname_dir(g_extern.savefile_name, g_extern.basename, ".srm", sizeof(g_extern.savefile_name)); } else RARCH_WARN("savefile_directory is not a directory, ignoring ...\n");