From 144493e88e8d2dc22ea9db26c56a0a492db9f5f3 Mon Sep 17 00:00:00 2001 From: jdgleaver Date: Thu, 23 Jan 2020 14:58:53 +0000 Subject: [PATCH] Add disk control interface v1 support --- frontend/libretro.c | 285 +++++++++++++++++++++++++---- libretro-common/include/libretro.h | 120 +++++++++++- 2 files changed, 370 insertions(+), 35 deletions(-) diff --git a/frontend/libretro.c b/frontend/libretro.c index ab854218..64e41452 100644 --- a/frontend/libretro.c +++ b/frontend/libretro.c @@ -848,15 +848,72 @@ void retro_cheat_set(unsigned index, bool enabled, const char *code) Cheats[index].Enabled = enabled; } +// just in case, maybe a win-rt port in the future? +#ifdef _WIN32 +#define SLASH '\\' +#else +#define SLASH '/' +#endif + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + /* multidisk support */ +static unsigned int disk_initial_index; +static char disk_initial_path[PATH_MAX]; static bool disk_ejected; static unsigned int disk_current_index; static unsigned int disk_count; static struct disks_state { char *fname; + char *flabel; int internal_index; // for multidisk eboots } disks[8]; +static void get_disk_label(char *disk_label, const char *disk_path, size_t len) +{ + const char *base = NULL; + + if (!disk_path || (*disk_path == '\0')) + return; + + base = strrchr(disk_path, SLASH); + if (!base) + base = disk_path; + + if (*base == SLASH) + base++; + + strncpy(disk_label, base, len - 1); + disk_label[len - 1] = '\0'; + + char *ext = strrchr(disk_label, '.'); + if (ext) + *ext = '\0'; +} + +static void disk_init(void) +{ + size_t i; + + disk_ejected = false; + disk_current_index = 0; + disk_count = 0; + + for (i = 0; i < sizeof(disks) / sizeof(disks[0]); i++) { + if (disks[i].fname != NULL) { + free(disks[i].fname); + disks[i].fname = NULL; + } + if (disks[i].flabel != NULL) { + free(disks[i].flabel); + disks[i].flabel = NULL; + } + disks[i].internal_index = 0; + } +} + static bool disk_set_eject_state(bool ejected) { // weird PCSX API.. @@ -927,18 +984,29 @@ static unsigned int disk_get_num_images(void) static bool disk_replace_image_index(unsigned index, const struct retro_game_info *info) { - char *old_fname; - bool ret = true; + char *old_fname = NULL; + char *old_flabel = NULL; + bool ret = true; if (index >= sizeof(disks) / sizeof(disks[0])) return false; - old_fname = disks[index].fname; - disks[index].fname = NULL; + old_fname = disks[index].fname; + old_flabel = disks[index].flabel; + + disks[index].fname = NULL; + disks[index].flabel = NULL; disks[index].internal_index = 0; if (info != NULL) { + char disk_label[PATH_MAX]; + disk_label[0] = '\0'; + disks[index].fname = strdup(info->path); + + get_disk_label(disk_label, info->path, PATH_MAX); + disks[index].flabel = strdup(disk_label); + if (index == disk_current_index) ret = disk_set_image_index(index); } @@ -946,6 +1014,9 @@ static bool disk_replace_image_index(unsigned index, if (old_fname != NULL) free(old_fname); + if (old_flabel != NULL) + free(old_flabel); + return ret; } @@ -958,6 +1029,64 @@ static bool disk_add_image_index(void) return true; } +static bool disk_set_initial_image(unsigned index, const char *path) +{ + if (index >= sizeof(disks) / sizeof(disks[0])) + return false; + + if (!path || (*path == '\0')) + return false; + + disk_initial_index = index; + + strncpy(disk_initial_path, path, sizeof(disk_initial_path) - 1); + disk_initial_path[sizeof(disk_initial_path) - 1] = '\0'; + + return true; +} + +static bool disk_get_image_path(unsigned index, char *path, size_t len) +{ + const char *fname = NULL; + + if (len < 1) + return false; + + if (index >= sizeof(disks) / sizeof(disks[0])) + return false; + + fname = disks[index].fname; + + if (!fname || (*fname == '\0')) + return false; + + strncpy(path, fname, len - 1); + path[len - 1] = '\0'; + + return true; +} + +static bool disk_get_image_label(unsigned index, char *label, size_t len) +{ + const char *flabel = NULL; + + if (len < 1) + return false; + + if (index >= sizeof(disks) / sizeof(disks[0])) + return false; + + flabel = disks[index].flabel; + + if (!flabel || (*flabel == '\0')) + return false; + + strncpy(label, flabel, len - 1); + label[len - 1] = '\0'; + + return true; +} + static struct retro_disk_control_callback disk_control = { .set_eject_state = disk_set_eject_state, .get_eject_state = disk_get_eject_state, @@ -968,16 +1097,18 @@ static struct retro_disk_control_callback disk_control = { .add_image_index = disk_add_image_index, }; -// just in case, maybe a win-rt port in the future? -#ifdef _WIN32 -#define SLASH '\\' -#else -#define SLASH '/' -#endif - -#ifndef PATH_MAX -#define PATH_MAX 4096 -#endif +static struct retro_disk_control_ext_callback disk_control_ext = { + .set_eject_state = disk_set_eject_state, + .get_eject_state = disk_get_eject_state, + .get_image_index = disk_get_image_index, + .set_image_index = disk_set_image_index, + .get_num_images = disk_get_num_images, + .replace_image_index = disk_replace_image_index, + .add_image_index = disk_add_image_index, + .set_initial_image = disk_set_initial_image, + .get_image_path = disk_get_image_path, + .get_image_label = disk_get_image_label, +}; static char base_dir[1024]; @@ -1001,8 +1132,16 @@ static bool read_m3u(const char *file) if (line[0] != '\0') { + char disk_label[PATH_MAX]; + disk_label[0] = '\0'; + snprintf(name, sizeof(name), "%s%c%s", base_dir, SLASH, line); - disks[disk_count++].fname = strdup(name); + disks[disk_count].fname = strdup(name); + + get_disk_label(disk_label, name, PATH_MAX); + disks[disk_count].flabel = strdup(disk_label); + + disk_count++; } } @@ -1073,6 +1212,7 @@ static void set_retro_memmap(void) bool retro_load_game(const struct retro_game_info *info) { size_t i; + unsigned int cd_index = 0; bool is_m3u = (strcasestr(info->path, ".m3u") != NULL); struct retro_input_descriptor desc[] = { @@ -1269,15 +1409,8 @@ bool retro_load_game(const struct retro_game_info *info) plugins_opened = 0; } - for (i = 0; i < sizeof(disks) / sizeof(disks[0]); i++) { - if (disks[i].fname != NULL) { - free(disks[i].fname); - disks[i].fname = NULL; - } - disks[i].internal_index = 0; - } + disk_init(); - disk_current_index = 0; extract_directory(base_dir, info->path, sizeof(base_dir)); if (is_m3u) { @@ -1286,11 +1419,30 @@ bool retro_load_game(const struct retro_game_info *info) return false; } } else { + char disk_label[PATH_MAX]; + disk_label[0] = '\0'; + disk_count = 1; disks[0].fname = strdup(info->path); + + get_disk_label(disk_label, info->path, PATH_MAX); + disks[0].flabel = strdup(disk_label); + } + + /* If this is an M3U file, attempt to set the + * initial disk image */ + if (is_m3u && + (disk_initial_index > 0) && + (disk_initial_index < disk_count)) { + const char *fname = disks[disk_initial_index].fname; + + if (fname && (*fname != '\0')) + if (strcmp(disk_initial_path, fname) == 0) + cd_index = disk_initial_index; } - set_cd_image(disks[0].fname); + set_cd_image(disks[cd_index].fname); + disk_current_index = cd_index; /* have to reload after set_cd_image for correct cdr plugin */ if (LoadPlugins() == -1) { @@ -1306,6 +1458,69 @@ bool retro_load_game(const struct retro_game_info *info) return false; } + /* Handle multi-disk images (i.e. PBP) + * > Cannot do this until after OpenPlugins() is + * called (since this sets the value of + * cdrIsoMultidiskCount) */ + if (!is_m3u && (cdrIsoMultidiskCount > 1)) { + disk_count = cdrIsoMultidiskCount < 8 ? cdrIsoMultidiskCount : 8; + + /* Small annoyance: We need to change the label + * of disk 0, so have to clear existing entries */ + if (disks[0].fname != NULL) + free(disks[0].fname); + disks[0].fname = NULL; + + if (disks[0].flabel != NULL) + free(disks[0].flabel); + disks[0].flabel = NULL; + + for (i = 0; i < sizeof(disks) / sizeof(disks[0]) && i < cdrIsoMultidiskCount; i++) { + char disk_name[PATH_MAX]; + char disk_label[PATH_MAX]; + disk_name[0] = '\0'; + disk_label[0] = '\0'; + + disks[i].fname = strdup(info->path); + + get_disk_label(disk_name, info->path, PATH_MAX); + snprintf(disk_label, sizeof(disk_label), "%s #%u", disk_name, (unsigned)i + 1); + disks[i].flabel = strdup(disk_label); + + disks[i].internal_index = i; + } + + /* This is not an M3U file, so initial disk + * image has not yet been set - attempt to + * do so now */ + if ((disk_initial_index > 0) && + (disk_initial_index < disk_count)) { + const char *fname = disks[disk_initial_index].fname; + + if (fname && (*fname != '\0')) + if (strcmp(disk_initial_path, fname) == 0) + cd_index = disk_initial_index; + } + + if (cd_index > 0) { + CdromId[0] = '\0'; + CdromLabel[0] = '\0'; + + cdrIsoMultidiskSelect = disks[cd_index].internal_index; + disk_current_index = cd_index; + set_cd_image(disks[cd_index].fname); + + if (ReloadCdromPlugin() < 0) { + log_cb(RETRO_LOG_INFO, "failed to reload cdr plugins\n"); + return false; + } + if (CDR_open() < 0) { + log_cb(RETRO_LOG_INFO, "failed to open cdr plugin\n"); + return false; + } + } + } + plugin_call_rearmed_cbs(); dfinput_activate(); @@ -1322,15 +1537,6 @@ bool retro_load_game(const struct retro_game_info *info) } emu_on_new_cd(0); - // multidisk images - if (!is_m3u) { - disk_count = cdrIsoMultidiskCount < 8 ? cdrIsoMultidiskCount : 8; - for (i = 1; i < sizeof(disks) / sizeof(disks[0]) && i < cdrIsoMultidiskCount; i++) { - disks[i].fname = strdup(info->path); - disks[i].internal_index = i; - } - } - set_retro_memmap(); return true; @@ -2411,6 +2617,7 @@ static void loadPSXBios(void) void retro_init(void) { + unsigned dci_version = 0; struct retro_rumble_interface rumble; int ret; @@ -2456,7 +2663,13 @@ void retro_init(void) loadPSXBios(); environ_cb(RETRO_ENVIRONMENT_GET_CAN_DUPE, &vout_can_dupe); - environ_cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE, &disk_control); + + disk_initial_index = 0; + disk_initial_path[0] = '\0'; + if (environ_cb(RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION, &dci_version) && (dci_version >= 1)) + environ_cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE, &disk_control_ext); + else + environ_cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE, &disk_control); rumble_cb = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble)) @@ -2501,6 +2714,10 @@ void retro_deinit(void) deinit_vita_mmap(); #endif libretro_supports_bitmasks = false; + + /* Have to reset disks struct, otherwise + * fnames/flabels will leak memory */ + disk_init(); } #ifdef VITA diff --git a/libretro-common/include/libretro.h b/libretro-common/include/libretro.h index d0f1042c..4ddb6e89 100644 --- a/libretro-common/include/libretro.h +++ b/libretro-common/include/libretro.h @@ -1246,6 +1246,47 @@ enum retro_mod * default when calling SET_VARIABLES/SET_CORE_OPTIONS. */ +#define RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER 56 + /* unsigned * -- + * + * Allows an implementation to ask frontend preferred hardware + * context to use. Core should use this information to deal + * with what specific context to request with SET_HW_RENDER. + * + * 'data' points to an unsigned variable + */ + +#define RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION 57 + /* unsigned * -- + * Unsigned value is the API version number of the disk control + * interface supported by the frontend. If callback return false, + * API version is assumed to be 0. + * + * In legacy code, the disk control interface is defined by passing + * a struct of type retro_disk_control_callback to + * RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE. + * This may be still be done regardless of the disk control + * interface version. + * + * If version is >= 1 however, the disk control interface may + * instead be defined by passing a struct of type + * retro_disk_control_ext_callback to + * RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE. + * This allows the core to provide additional information about + * disk images to the frontend and/or enables extra + * disk control functionality by the frontend. + */ + +#define RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE 58 + /* const struct retro_disk_control_ext_callback * -- + * Sets an interface which frontend can use to eject and insert + * disk images, and also obtain information about individual + * disk image files registered by the core. + * This is used for games which consist of multiple images and + * must be manually swapped out by the user (e.g. PSX, floppy disk + * based systems). + */ + /* VFS functionality */ /* File paths: @@ -1922,6 +1963,10 @@ enum retro_sensor_action { RETRO_SENSOR_ACCELEROMETER_ENABLE = 0, RETRO_SENSOR_ACCELEROMETER_DISABLE, + RETRO_SENSOR_GYROSCOPE_ENABLE, + RETRO_SENSOR_GYROSCOPE_DISABLE, + RETRO_SENSOR_ILLUMINANCE_ENABLE, + RETRO_SENSOR_ILLUMINANCE_DISABLE, RETRO_SENSOR_DUMMY = INT_MAX }; @@ -1930,6 +1975,10 @@ enum retro_sensor_action #define RETRO_SENSOR_ACCELEROMETER_X 0 #define RETRO_SENSOR_ACCELEROMETER_Y 1 #define RETRO_SENSOR_ACCELEROMETER_Z 2 +#define RETRO_SENSOR_GYROSCOPE_X 3 +#define RETRO_SENSOR_GYROSCOPE_Y 4 +#define RETRO_SENSOR_GYROSCOPE_Z 5 +#define RETRO_SENSOR_ILLUMINANCE 6 typedef bool (RETRO_CALLCONV *retro_set_sensor_state_t)(unsigned port, enum retro_sensor_action action, unsigned rate); @@ -2287,7 +2336,8 @@ struct retro_keyboard_callback retro_keyboard_event_t callback; }; -/* Callbacks for RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE. +/* Callbacks for RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE & + * RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE. * Should be set for implementations which can swap out multiple disk * images in runtime. * @@ -2345,6 +2395,53 @@ typedef bool (RETRO_CALLCONV *retro_replace_image_index_t)(unsigned index, * with replace_image_index. */ typedef bool (RETRO_CALLCONV *retro_add_image_index_t)(void); +/* Sets initial image to insert in drive when calling + * core_load_game(). + * Since we cannot pass the initial index when loading + * content (this would require a major API change), this + * is set by the frontend *before* calling the core's + * retro_load_game()/retro_load_game_special() implementation. + * A core should therefore cache the index/path values and handle + * them inside retro_load_game()/retro_load_game_special(). + * - If 'index' is invalid (index >= get_num_images()), the + * core should ignore the set value and instead use 0 + * - 'path' is used purely for error checking - i.e. when + * content is loaded, the core should verify that the + * disk specified by 'index' has the specified file path. + * This is to guard against auto selecting the wrong image + * if (for example) the user should modify an existing M3U + * playlist. We have to let the core handle this because + * set_initial_image() must be called before loading content, + * i.e. the frontend cannot access image paths in advance + * and thus cannot perform the error check itself. + * If set path and content path do not match, the core should + * ignore the set 'index' value and instead use 0 + * Returns 'false' if index or 'path' are invalid, or core + * does not support this functionality + */ +typedef bool (RETRO_CALLCONV *retro_set_initial_image_t)(unsigned index, const char *path); + +/* Fetches the path of the specified disk image file. + * Returns 'false' if index is invalid (index >= get_num_images()) + * or path is otherwise unavailable. + */ +typedef bool (RETRO_CALLCONV *retro_get_image_path_t)(unsigned index, char *path, size_t len); + +/* Fetches a core-provided 'label' for the specified disk + * image file. In the simplest case this may be a file name + * (without extension), but for cores with more complex + * content requirements information may be provided to + * facilitate user disk swapping - for example, a core + * running floppy-disk-based content may uniquely label + * save disks, data disks, level disks, etc. with names + * corresponding to in-game disk change prompts (so the + * frontend can provide better user guidance than a 'dumb' + * disk index value). + * Returns 'false' if index is invalid (index >= get_num_images()) + * or label is otherwise unavailable. + */ +typedef bool (RETRO_CALLCONV *retro_get_image_label_t)(unsigned index, char *label, size_t len); + struct retro_disk_control_callback { retro_set_eject_state_t set_eject_state; @@ -2358,6 +2455,27 @@ struct retro_disk_control_callback retro_add_image_index_t add_image_index; }; +struct retro_disk_control_ext_callback +{ + retro_set_eject_state_t set_eject_state; + retro_get_eject_state_t get_eject_state; + + retro_get_image_index_t get_image_index; + retro_set_image_index_t set_image_index; + retro_get_num_images_t get_num_images; + + retro_replace_image_index_t replace_image_index; + retro_add_image_index_t add_image_index; + + /* NOTE: Frontend will only attempt to record/restore + * last used disk index if both set_initial_image() + * and get_image_path() are implemented */ + retro_set_initial_image_t set_initial_image; /* Optional - may be NULL */ + + retro_get_image_path_t get_image_path; /* Optional - may be NULL */ + retro_get_image_label_t get_image_label; /* Optional - may be NULL */ +}; + enum retro_pixel_format { /* 0RGB1555, native endian. -- 2.39.5