Add disk control interface v1 support
authorjdgleaver <james@leaver.myzen.co.uk>
Thu, 23 Jan 2020 14:58:53 +0000 (14:58 +0000)
committerjdgleaver <james@leaver.myzen.co.uk>
Thu, 23 Jan 2020 14:58:53 +0000 (14:58 +0000)
frontend/libretro.c
libretro-common/include/libretro.h

index ab85421..64e4145 100644 (file)
@@ -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
index d0f1042..4ddb6e8 100644 (file)
@@ -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.