1 /* Copyright  (C) 2010-2020 The RetroArch team
2  *
3  * ---------------------------------------------------------------------------------------
4  * The following license statement only applies to this file (disk_control_interface.c).
5  * ---------------------------------------------------------------------------------------
6  *
7  * Permission is hereby granted, free of charge,
8  * to any person obtaining a copy of this software and associated documentation files (the "Software"),
9  * to deal in the Software without restriction, including without limitation the rights to
10  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
11  * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include <string/stdstring.h>
24 #include <file/file_path.h>
25 
26 #include "paths.h"
27 #include "retroarch.h"
28 #include "verbosity.h"
29 #include "msg_hash.h"
30 
31 #include "disk_control_interface.h"
32 
33 /*****************/
34 /* Configuration */
35 /*****************/
36 
37 /* Sets all disk interface callback functions
38  * to NULL */
disk_control_reset_callback(disk_control_interface_t * disk_control)39 static void disk_control_reset_callback(
40       disk_control_interface_t *disk_control)
41 {
42    if (!disk_control)
43       return;
44 
45    memset(&disk_control->cb, 0,
46          sizeof(struct retro_disk_control_ext_callback));
47 }
48 
49 /* Set v0 disk interface callback functions */
disk_control_set_callback(disk_control_interface_t * disk_control,const struct retro_disk_control_callback * cb)50 void disk_control_set_callback(
51       disk_control_interface_t *disk_control,
52       const struct retro_disk_control_callback *cb)
53 {
54    if (!disk_control)
55       return;
56 
57    disk_control_reset_callback(disk_control);
58 
59    if (!cb)
60       return;
61 
62    disk_control->cb.set_eject_state     = cb->set_eject_state;
63    disk_control->cb.get_eject_state     = cb->get_eject_state;
64    disk_control->cb.get_image_index     = cb->get_image_index;
65    disk_control->cb.set_image_index     = cb->set_image_index;
66    disk_control->cb.get_num_images      = cb->get_num_images;
67    disk_control->cb.replace_image_index = cb->replace_image_index;
68    disk_control->cb.add_image_index     = cb->add_image_index;
69 }
70 
71 /* Set v1+ disk interface callback functions */
disk_control_set_ext_callback(disk_control_interface_t * disk_control,const struct retro_disk_control_ext_callback * cb)72 void disk_control_set_ext_callback(
73       disk_control_interface_t *disk_control,
74       const struct retro_disk_control_ext_callback *cb)
75 {
76    if (!disk_control)
77       return;
78 
79    disk_control_reset_callback(disk_control);
80 
81    if (!cb)
82       return;
83 
84    disk_control->cb.set_eject_state     = cb->set_eject_state;
85    disk_control->cb.get_eject_state     = cb->get_eject_state;
86    disk_control->cb.get_image_index     = cb->get_image_index;
87    disk_control->cb.set_image_index     = cb->set_image_index;
88    disk_control->cb.get_num_images      = cb->get_num_images;
89    disk_control->cb.replace_image_index = cb->replace_image_index;
90    disk_control->cb.add_image_index     = cb->add_image_index;
91 
92    disk_control->cb.set_initial_image   = cb->set_initial_image;
93    disk_control->cb.get_image_path      = cb->get_image_path;
94    disk_control->cb.get_image_label     = cb->get_image_label;
95 }
96 
97 /**********/
98 /* Status */
99 /**********/
100 
101 /* Returns true if core supports basic disk
102  * control functionality
103  * - set_eject_state
104  * - get_eject_state
105  * - get_image_index
106  * - set_image_index
107  * - get_num_images */
disk_control_enabled(disk_control_interface_t * disk_control)108 bool disk_control_enabled(
109       disk_control_interface_t *disk_control)
110 {
111    if (!disk_control)
112       return false;
113 
114    if (disk_control->cb.set_eject_state &&
115        disk_control->cb.get_eject_state &&
116        disk_control->cb.get_image_index &&
117        disk_control->cb.set_image_index &&
118        disk_control->cb.get_num_images)
119       return true;
120 
121    return false;
122 }
123 
124 /* Returns true if core supports disk append
125  * functionality
126  * - replace_image_index
127  * - add_image_index */
disk_control_append_enabled(disk_control_interface_t * disk_control)128 bool disk_control_append_enabled(
129       disk_control_interface_t *disk_control)
130 {
131    if (!disk_control)
132       return false;
133 
134    if (disk_control->cb.replace_image_index &&
135        disk_control->cb.add_image_index)
136       return true;
137 
138    return false;
139 }
140 
141 /* Returns true if core supports image
142  * labels
143  * - get_image_label */
disk_control_image_label_enabled(disk_control_interface_t * disk_control)144 bool disk_control_image_label_enabled(
145       disk_control_interface_t *disk_control)
146 {
147    if (!disk_control || !disk_control->cb.get_image_label)
148       return false;
149    return true;
150 }
151 
152 /* Returns true if core supports setting
153  * initial disk index
154  * - set_initial_image
155  * - get_image_path */
disk_control_initial_image_enabled(disk_control_interface_t * disk_control)156 bool disk_control_initial_image_enabled(
157       disk_control_interface_t *disk_control)
158 {
159    if (!disk_control)
160       return false;
161 
162    if (disk_control->cb.set_initial_image &&
163        disk_control->cb.get_image_path)
164       return true;
165 
166    return false;
167 }
168 
169 /***********/
170 /* Getters */
171 /***********/
172 
173 /* Returns true if disk is currently ejected */
disk_control_get_eject_state(disk_control_interface_t * disk_control)174 bool disk_control_get_eject_state(
175       disk_control_interface_t *disk_control)
176 {
177    if (!disk_control || !disk_control->cb.get_eject_state)
178       return false;
179    return disk_control->cb.get_eject_state();
180 }
181 
182 /* Returns number of disk images registered
183  * by the core */
disk_control_get_num_images(disk_control_interface_t * disk_control)184 unsigned disk_control_get_num_images(
185       disk_control_interface_t *disk_control)
186 {
187    if (!disk_control || !disk_control->cb.get_num_images)
188       return 0;
189    return disk_control->cb.get_num_images();
190 }
191 
192 /* Returns currently selected disk image index */
disk_control_get_image_index(disk_control_interface_t * disk_control)193 unsigned disk_control_get_image_index(
194       disk_control_interface_t *disk_control)
195 {
196    if (!disk_control || !disk_control->cb.get_image_index)
197       return 0;
198    return disk_control->cb.get_image_index();
199 }
200 
201 /* Fetches core-provided disk image label
202  * (label is set to an empty string if core
203  * does not support image labels) */
disk_control_get_image_label(disk_control_interface_t * disk_control,unsigned index,char * label,size_t len)204 void disk_control_get_image_label(
205       disk_control_interface_t *disk_control,
206       unsigned index, char *label, size_t len)
207 {
208    if (!label || len < 1)
209       return;
210 
211    if (!disk_control)
212       goto error;
213 
214    if (!disk_control->cb.get_image_label)
215       goto error;
216 
217    if (!disk_control->cb.get_image_label(index, label, len))
218       goto error;
219 
220    return;
221 
222 error:
223    label[0] = '\0';
224 }
225 
226 /***********/
227 /* Setters */
228 /***********/
229 
230 /* Generates an appropriate log/notification message
231  * for a disk index change event */
disk_control_get_index_set_msg(disk_control_interface_t * disk_control,unsigned num_images,unsigned index,bool success,unsigned * msg_duration,char * msg,size_t len)232 static void disk_control_get_index_set_msg(
233       disk_control_interface_t *disk_control,
234       unsigned num_images, unsigned index, bool success,
235       unsigned *msg_duration, char *msg, size_t len)
236 {
237    bool has_label = false;
238    char image_label[128];
239 
240    image_label[0] = '\0';
241 
242    if (!disk_control || !msg_duration || !msg || len < 1)
243       return;
244 
245    /* Attempt to get image label */
246    if (index < num_images)
247    {
248       disk_control_get_image_label(
249             disk_control, index, image_label, sizeof(image_label));
250       has_label = !string_is_empty(image_label);
251    }
252 
253    /* Get message duration
254     * > Default is 60
255     * > If a label is shown, then increase duration by 50%
256     * > For errors, duration is always 180 */
257    *msg_duration = success ?
258          (has_label ? 90 : 60) :
259          180;
260 
261    /* Check whether image was inserted or removed */
262    if (index < num_images)
263    {
264       if (has_label)
265          snprintf(
266                msg, len, "%s: %u/%u - %s",
267                success ? msg_hash_to_str(MSG_SETTING_DISK_IN_TRAY) :
268                      msg_hash_to_str(MSG_FAILED_TO_SET_DISK),
269                index + 1, num_images, image_label);
270       else
271          snprintf(
272                msg, len, "%s: %u/%u",
273                success ? msg_hash_to_str(MSG_SETTING_DISK_IN_TRAY) :
274                      msg_hash_to_str(MSG_FAILED_TO_SET_DISK),
275                index + 1, num_images);
276    }
277    else
278       strlcpy(
279             msg,
280             success ? msg_hash_to_str(MSG_REMOVED_DISK_FROM_TRAY) :
281                   msg_hash_to_str(MSG_FAILED_TO_REMOVE_DISK_FROM_TRAY),
282             len);
283 }
284 
285 /* Sets the eject state of the virtual disk tray */
disk_control_set_eject_state(disk_control_interface_t * disk_control,bool eject,bool verbosity)286 bool disk_control_set_eject_state(
287       disk_control_interface_t *disk_control,
288       bool eject, bool verbosity)
289 {
290    bool error = false;
291    char msg[128];
292 
293    msg[0] = '\0';
294 
295    if (!disk_control || !disk_control->cb.set_eject_state)
296       return false;
297 
298    /* Set eject state */
299    if (disk_control->cb.set_eject_state(eject))
300       snprintf(
301             msg, sizeof(msg), "%s %s",
302             eject ? msg_hash_to_str(MSG_DISK_EJECTED) :
303                   msg_hash_to_str(MSG_DISK_CLOSED),
304             msg_hash_to_str(MSG_VIRTUAL_DISK_TRAY));
305    else
306    {
307       error = true;
308       snprintf(
309             msg, sizeof(msg), "%s %s %s",
310             msg_hash_to_str(MSG_FAILED_TO),
311             eject ? msg_hash_to_str(MSG_VIRTUAL_DISK_TRAY_EJECT) :
312                   msg_hash_to_str(MSG_VIRTUAL_DISK_TRAY_CLOSE),
313             msg_hash_to_str(MSG_VIRTUAL_DISK_TRAY));
314    }
315 
316    if (!string_is_empty(msg))
317    {
318       if (error)
319          RARCH_ERR("%s\n", msg);
320       else
321          RARCH_LOG("%s\n", msg);
322 
323       /* Errors should always be displayed */
324       if (verbosity || error)
325          runloop_msg_queue_push(
326                msg, 1, error ? 180 : 60,
327                true, NULL,
328                MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
329    }
330 
331    return !error;
332 }
333 
334 /* Sets currently selected disk index
335  * NOTE: Will fail if disk is not currently ejected */
disk_control_set_index(disk_control_interface_t * disk_control,unsigned index,bool verbosity)336 bool disk_control_set_index(
337       disk_control_interface_t *disk_control,
338       unsigned index, bool verbosity)
339 {
340    bool error            = false;
341    unsigned num_images   = 0;
342    unsigned msg_duration = 0;
343    char msg[PATH_MAX_LENGTH];
344 
345    msg[0] = '\0';
346 
347    if (!disk_control)
348       return false;
349 
350    if (!disk_control->cb.get_eject_state ||
351        !disk_control->cb.get_num_images ||
352        !disk_control->cb.set_image_index)
353       return false;
354 
355    /* Ensure that disk is currently ejected */
356    if (!disk_control->cb.get_eject_state())
357       return false;
358 
359    /* Get current number of disk images */
360    num_images = disk_control->cb.get_num_images();
361 
362    /* Perform 'set index' action */
363    error = !disk_control->cb.set_image_index(index);
364 
365    /* Get log/notification message */
366    disk_control_get_index_set_msg(
367          disk_control, num_images, index, !error,
368          &msg_duration, msg, sizeof(msg));
369 
370    /* Output log/notification message */
371    if (!string_is_empty(msg))
372    {
373       if (error)
374          RARCH_ERR("%s\n", msg);
375       else
376          RARCH_LOG("%s\n", msg);
377 
378       /* Errors should always be displayed */
379       if (verbosity || error)
380          runloop_msg_queue_push(
381                msg, 1, msg_duration,
382                true, NULL,
383                MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
384    }
385 
386    /* If operation was successful, update disk
387     * index record (if enabled) */
388    if (!error && disk_control->record_enabled)
389    {
390       if (disk_control->cb.get_image_index &&
391           disk_control->cb.get_image_path)
392       {
393          bool image_path_valid    = false;
394          unsigned new_image_index = 0;
395          char new_image_path[PATH_MAX_LENGTH];
396 
397          new_image_path[0] = '\0';
398 
399          /* Get current image index + path */
400          new_image_index  = disk_control->cb.get_image_index();
401          image_path_valid = disk_control->cb.get_image_path(
402                new_image_index, new_image_path, sizeof(new_image_path));
403 
404          if (image_path_valid)
405             disk_index_file_set(
406                   &disk_control->index_record,
407                   new_image_index, new_image_path);
408          else
409             disk_index_file_set(
410                   &disk_control->index_record, 0, NULL);
411       }
412    }
413 
414    return !error;
415 }
416 
417 /* Increments selected disk index */
disk_control_set_index_next(disk_control_interface_t * disk_control,bool verbosity)418 bool disk_control_set_index_next(
419       disk_control_interface_t *disk_control,
420       bool verbosity)
421 {
422    unsigned num_images   = 0;
423    unsigned image_index  = 0;
424    bool disk_next_enable = false;
425 
426    if (!disk_control)
427       return false;
428 
429    if (!disk_control->cb.get_num_images ||
430        !disk_control->cb.get_image_index)
431       return false;
432 
433    num_images  = disk_control->cb.get_num_images();
434    image_index = disk_control->cb.get_image_index();
435    /* Would seem more sensible to check (num_images > 1)
436     * here, but seems we need to be able to cycle the
437     * same image for legacy reasons... */
438    disk_next_enable = (num_images > 0) && (num_images != UINT_MAX);
439 
440    if (!disk_next_enable)
441    {
442       RARCH_ERR("%s.\n", msg_hash_to_str(MSG_GOT_INVALID_DISK_INDEX));
443       return false;
444    }
445 
446    if (image_index < (num_images - 1))
447       image_index++;
448 
449    return disk_control_set_index(disk_control, image_index, verbosity);
450 }
451 
452 /* Decrements selected disk index */
disk_control_set_index_prev(disk_control_interface_t * disk_control,bool verbosity)453 bool disk_control_set_index_prev(
454       disk_control_interface_t *disk_control,
455       bool verbosity)
456 {
457    unsigned num_images   = 0;
458    unsigned image_index  = 0;
459    bool disk_prev_enable = false;
460 
461    if (!disk_control)
462       return false;
463 
464    if (!disk_control->cb.get_num_images ||
465        !disk_control->cb.get_image_index)
466       return false;
467 
468    num_images  = disk_control->cb.get_num_images();
469    image_index = disk_control->cb.get_image_index();
470    /* Would seem more sensible to check (num_images > 1)
471     * here, but seems we need to be able to cycle the
472     * same image for legacy reasons... */
473    disk_prev_enable = (num_images > 0);
474 
475    if (!disk_prev_enable)
476    {
477       RARCH_ERR("%s.\n", msg_hash_to_str(MSG_GOT_INVALID_DISK_INDEX));
478       return false;
479    }
480 
481    if (image_index > 0)
482       image_index--;
483 
484    return disk_control_set_index(disk_control, image_index, verbosity);
485 }
486 
487 /* Appends specified image file to disk image list */
disk_control_append_image(disk_control_interface_t * disk_control,const char * image_path)488 bool disk_control_append_image(
489       disk_control_interface_t *disk_control,
490       const char *image_path)
491 {
492    bool initial_disk_ejected   = false;
493    unsigned initial_index      = 0;
494    unsigned new_index          = 0;
495    const char *image_filename  = NULL;
496    struct retro_game_info info = {0};
497    char msg[128];
498 
499    msg[0] = '\0';
500 
501    /* Sanity check. If any of these fail then a
502     * frontend error has occurred - we will not
503     * deal with that here */
504    if (!disk_control)
505       return false;
506 
507    if (!disk_control->cb.get_image_index ||
508        !disk_control->cb.get_num_images ||
509        !disk_control->cb.add_image_index ||
510        !disk_control->cb.replace_image_index ||
511        !disk_control->cb.get_eject_state)
512       return false;
513 
514    if (string_is_empty(image_path))
515       return false;
516 
517    image_filename = path_basename(image_path);
518 
519    if (string_is_empty(image_filename))
520       return false;
521 
522    /* Get initial disk eject state */
523    initial_disk_ejected = disk_control_get_eject_state(disk_control);
524 
525    /* Cache initial image index */
526    initial_index = disk_control->cb.get_image_index();
527 
528    /* If tray is currently closed, eject disk */
529    if (!initial_disk_ejected &&
530        !disk_control_set_eject_state(disk_control, true, false))
531       goto error;
532 
533    /* Append image */
534    if (!disk_control->cb.add_image_index())
535       goto error;
536 
537    new_index = disk_control->cb.get_num_images();
538    if (new_index < 1)
539       goto error;
540    new_index--;
541 
542    info.path = image_path;
543    if (!disk_control->cb.replace_image_index(new_index, &info))
544       goto error;
545 
546    /* Set new index */
547    if (!disk_control_set_index(disk_control, new_index, false))
548       goto error;
549 
550    /* If tray was initially closed, insert disk
551     * (i.e. leave system in the state we found it) */
552    if (!initial_disk_ejected &&
553        !disk_control_set_eject_state(disk_control, false, false))
554       goto error;
555 
556    /* Display log */
557    snprintf(
558          msg, sizeof(msg), "%s: %s",
559          msg_hash_to_str(MSG_APPENDED_DISK), image_filename);
560 
561    RARCH_LOG("%s\n", msg);
562    /* This message should always be displayed, since
563     * the menu itself does not provide sufficient
564     * visual feedback */
565    runloop_msg_queue_push(
566          msg, 0, 120,
567          true, NULL,
568          MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
569 
570    return true;
571 
572 error:
573    /* If we reach this point then everything is
574     * broken and the disk control interface is
575     * in an undefined state. Try to restore some
576     * sanity by reinserting the original disk...
577     * NOTE: If this fails then it's game over -
578     * just display the error notification and
579     * hope for the best... */
580    if (!disk_control->cb.get_eject_state())
581       disk_control_set_eject_state(disk_control, true, false);
582    disk_control_set_index(disk_control, initial_index, false);
583    if (!initial_disk_ejected)
584       disk_control_set_eject_state(disk_control, false, false);
585 
586    snprintf(
587          msg, sizeof(msg), "%s: %s",
588          msg_hash_to_str(MSG_FAILED_TO_APPEND_DISK), image_filename);
589 
590    runloop_msg_queue_push(
591          msg, 0, 180,
592          true, NULL,
593          MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
594 
595    return false;
596 }
597 
598 /*****************************/
599 /* 'Initial index' functions */
600 /*****************************/
601 
602 /* Attempts to set current core's initial disk index.
603  * > disk_control->record_enabled will be set to
604  *   'false' if core does not support initial
605  *   index functionality
606  * > disk_control->index_record will be loaded
607  *   from file (if an existing record is found)
608  * NOTE: Must be called immediately before
609  * loading content */
disk_control_set_initial_index(disk_control_interface_t * disk_control,const char * content_path,const char * dir_savefile)610 bool disk_control_set_initial_index(
611       disk_control_interface_t *disk_control,
612       const char *content_path,
613       const char *dir_savefile)
614 {
615    if (!disk_control)
616       return false;
617 
618    if (string_is_empty(content_path))
619       goto error;
620 
621    /* Check that 'initial index' functionality is enabled */
622    if (!disk_control->cb.set_initial_image ||
623        !disk_control->cb.get_num_images ||
624        !disk_control->cb.get_image_index ||
625        !disk_control->cb.get_image_path)
626       goto error;
627 
628    /* Attempt to initialise disk index record (reading
629     * from disk, if file exists) */
630    disk_control->record_enabled = disk_index_file_init(
631          &disk_control->index_record,
632          content_path, dir_savefile);
633 
634    /* If record is enabled and initial index is *not*
635     * zero, notify current core */
636    if (disk_control->record_enabled &&
637        (disk_control->index_record.image_index != 0))
638    {
639       if (!disk_control->cb.set_initial_image(
640             disk_control->index_record.image_index,
641             disk_control->index_record.image_path))
642       {
643          /* Note: We don't bother with an on-screen
644           * notification at this stage, since an error
645           * here may not matter (have to wait until
646           * disk index is verified) */
647          RARCH_ERR(
648                "Failed to set initial disk index: [%u] %s\n",
649                disk_control->index_record.image_index,
650                disk_control->index_record.image_path);
651          return false;
652       }
653    }
654 
655    return true;
656 
657 error:
658    disk_control->record_enabled = false;
659    return false;
660 }
661 
662 /* Checks that initial index has been set correctly
663  * and provides user notification.
664  * > Sets disk_control->initial_num_images if
665  *   if functionality is supported by core
666  * NOTE: Must be called immediately after
667  * loading content */
disk_control_verify_initial_index(disk_control_interface_t * disk_control,bool verbosity)668 bool disk_control_verify_initial_index(
669       disk_control_interface_t *disk_control,
670       bool verbosity)
671 {
672    bool success         = false;
673    unsigned image_index = 0;
674    char image_path[PATH_MAX_LENGTH];
675 
676    image_path[0] = '\0';
677 
678    if (!disk_control)
679       return false;
680 
681    /* If index record is disabled, can return immediately */
682    if (!disk_control->record_enabled)
683       return false;
684 
685    /* Check that 'initial index' functionality is enabled */
686    if (!disk_control->cb.set_initial_image ||
687        !disk_control->cb.get_num_images ||
688        !disk_control->cb.get_image_index ||
689        !disk_control->cb.get_image_path)
690       return false;
691 
692    /* Cache initial number of images
693     * (required for error checking when saving
694     * disk index file) */
695    disk_control->initial_num_images =
696          disk_control->cb.get_num_images();
697 
698    /* Get current image index + path */
699    image_index = disk_control->cb.get_image_index();
700 
701    if (disk_control->cb.get_image_path(
702          image_index, image_path, sizeof(image_path)))
703    {
704       /* Check whether index + path match set
705        * values
706        * > Note that if set index was zero and
707        *   set path was empty, we ignore the path
708        *   read here (since this corresponds to a
709        *   'first run', where no existing disk index
710        *   file was present) */
711       if ((image_index == disk_control->index_record.image_index) &&
712           (string_is_equal(image_path, disk_control->index_record.image_path) ||
713                ((disk_control->index_record.image_index == 0) &&
714                 string_is_empty(disk_control->index_record.image_path))))
715          success = true;
716    }
717 
718    /* If current disk is incorrect, notify user */
719    if (!success)
720    {
721       RARCH_ERR(
722                "Failed to set initial disk index:\n> Expected [%u] %s\n> Detected [%u] %s\n",
723                disk_control->index_record.image_index + 1,
724                disk_control->index_record.image_path,
725                image_index + 1,
726                image_path);
727 
728       /* Ignore 'verbosity' setting - errors should
729        * always be displayed */
730       runloop_msg_queue_push(
731             msg_hash_to_str(MSG_FAILED_TO_SET_INITIAL_DISK),
732             0, 60,
733             true, NULL,
734             MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
735 
736       /* Since a failure here typically means that the
737        * original M3U content file has been altered,
738        * any existing disk index record file will be
739        * invalid. We therefore 'reset' and save the disk
740        * index record to prevent a repeat of the error on
741        * the next run */
742       disk_index_file_set(&disk_control->index_record, 0, NULL);
743       disk_index_file_save(&disk_control->index_record);
744    }
745    /* If current disk is correct and recorded image
746     * path is empty (i.e. first run), need to register
747     * current image path */
748    else if (string_is_empty(disk_control->index_record.image_path))
749       disk_index_file_set(
750             &disk_control->index_record, image_index, image_path);
751 
752    /* Regardless of success/failure, notify user of
753     * current disk index *if* more than one disk
754     * is available */
755    if (disk_control->initial_num_images > 1)
756    {
757       unsigned msg_duration = 0;
758       char msg[PATH_MAX_LENGTH];
759 
760       msg[0] = '\0';
761 
762       disk_control_get_index_set_msg(
763             disk_control, disk_control->initial_num_images, image_index, true,
764             &msg_duration, msg, sizeof(msg));
765 
766       RARCH_LOG("%s\n", msg);
767 
768       /* Note: Do not flush message queue here, since
769        * it is likely other notifications will be
770        * generated before setting the disk index, and
771        * we do not want to 'overwrite' them */
772       if (verbosity)
773          runloop_msg_queue_push(
774                msg,
775                0, msg_duration,
776                false, NULL,
777                MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
778    }
779 
780    return success;
781 }
782 
783 /* Saves current disk index to file, if supported
784  * by current core */
disk_control_save_image_index(disk_control_interface_t * disk_control)785 bool disk_control_save_image_index(
786       disk_control_interface_t *disk_control)
787 {
788    if (!disk_control)
789       return false;
790 
791    /* If index record is disabled, can return immediately */
792    if (!disk_control->record_enabled)
793       return false;
794 
795    /* If core started with less than two disks,
796     * then a disk index record is unnecessary */
797    if (disk_control->initial_num_images < 2)
798       return false;
799 
800    /* If current index is greater than initial
801     * number of disks then user has appended a
802     * disk and it is currently active. This setup
803     * *cannot* be restored, so cancel the file save */
804    if (disk_control->index_record.image_index >=
805          disk_control->initial_num_images)
806       return false;
807 
808    /* Save record */
809    return disk_index_file_save(&disk_control->index_record);
810 }
811