Add libretro VFS and use VFS for windows target
[pcsx_rearmed.git] / libretro-common / vfs / vfs_implementation.c
1 /* Copyright  (C) 2010-2020 The RetroArch team
2 *
3 * ---------------------------------------------------------------------------------------
4 * The following license statement only applies to this file (vfs_implementation.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 <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <sys/types.h>
28
29 #include <string/stdstring.h>
30
31 #ifdef HAVE_CONFIG_H
32 #include "config.h"
33 #endif
34
35 #if defined(_WIN32)
36 #  ifdef _MSC_VER
37 #    define setmode _setmode
38 #  endif
39 #include <sys/stat.h>
40 #  ifdef _XBOX
41 #    include <xtl.h>
42 #    define INVALID_FILE_ATTRIBUTES -1
43 #  else
44
45 #    include <fcntl.h>
46 #    include <direct.h>
47 #    include <windows.h>
48 #  endif
49 #    include <io.h>
50 #else
51 #  if defined(PSP)
52 #    include <pspiofilemgr.h>
53 #  endif
54 #  include <sys/types.h>
55 #  include <sys/stat.h>
56 #  if !defined(VITA)
57 #  include <dirent.h>
58 #  endif
59 #  include <unistd.h>
60 #  if defined(ORBIS)
61 #  include <sys/fcntl.h>
62 #  include <sys/dirent.h>
63 #  include <orbisFile.h>
64 #  endif
65 #endif
66
67 #if defined (__CELLOS_LV2__)  && !defined(__PSL1GHT__)
68 #include <cell/cell_fs.h>
69 #define O_RDONLY CELL_FS_O_RDONLY
70 #define O_WRONLY CELL_FS_O_WRONLY
71 #define O_CREAT CELL_FS_O_CREAT
72 #define O_TRUNC CELL_FS_O_TRUNC
73 #define O_RDWR CELL_FS_O_RDWR
74 #else
75 #include <fcntl.h>
76 #endif
77
78 /* TODO: Some things are duplicated but I'm really afraid of breaking other platforms by touching this */
79 #if defined(VITA)
80 #  include <psp2/io/fcntl.h>
81 #  include <psp2/io/dirent.h>
82 #  include <psp2/io/stat.h>
83 #elif defined(ORBIS)
84 #  include <orbisFile.h>
85 #  include <ps4link.h>
86 #  include <sys/dirent.h>
87 #  include <sys/fcntl.h>
88 #elif !defined(_WIN32)
89 #  if defined(PSP)
90 #    include <pspiofilemgr.h>
91 #  endif
92 #  include <sys/types.h>
93 #  include <sys/stat.h>
94 #  include <dirent.h>
95 #  include <unistd.h>
96 #endif
97
98 #if (defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)) || defined(__QNX__) || defined(PSP)
99 #include <unistd.h> /* stat() is defined here */
100 #endif
101
102 #ifdef __APPLE__
103 #include <CoreFoundation/CoreFoundation.h>
104 #endif
105 #ifdef __HAIKU__
106 #include <kernel/image.h>
107 #endif
108 #ifndef __MACH__
109 #include <compat/strl.h>
110 #include <compat/posix_string.h>
111 #endif
112 #include <compat/strcasestr.h>
113 #include <retro_miscellaneous.h>
114 #include <encodings/utf.h>
115
116 #if defined(_WIN32)
117 #ifndef _XBOX
118 #if defined(_MSC_VER) && _MSC_VER <= 1200
119 #define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
120 #endif
121 #endif
122 #elif defined(VITA)
123 #define SCE_ERROR_ERRNO_EEXIST 0x80010011
124 #include <psp2/io/fcntl.h>
125 #include <psp2/io/dirent.h>
126 #include <psp2/io/stat.h>
127 #else
128 #include <sys/types.h>
129 #include <sys/stat.h>
130 #include <unistd.h>
131 #endif
132
133 #if defined(ORBIS)
134 #include <orbisFile.h>
135 #include <sys/fcntl.h>
136 #include <sys/dirent.h>
137 #endif
138 #if defined(PSP)
139 #include <pspkernel.h>
140 #endif
141
142 #if defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
143 #include <cell/cell_fs.h>
144 #endif
145
146 #if defined(VITA)
147 #define FIO_S_ISDIR SCE_S_ISDIR
148 #endif
149
150 #if (defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)) || defined(__QNX__) || defined(PSP)
151 #include <unistd.h> /* stat() is defined here */
152 #endif
153
154 /* Assume W-functions do not work below Win2K and Xbox platforms */
155 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 || defined(_XBOX)
156
157 #ifndef LEGACY_WIN32
158 #define LEGACY_WIN32
159 #endif
160
161 #endif
162
163 #if defined(_WIN32)
164 #if defined(_MSC_VER) && _MSC_VER >= 1400
165 #define ATLEAST_VC2005
166 #endif
167 #endif
168
169 #include <vfs/vfs_implementation.h>
170 #include <libretro.h>
171 #include <memmap.h>
172 #include <encodings/utf.h>
173 #include <compat/fopen_utf8.h>
174 #include <file/file_path.h>
175
176 #ifdef HAVE_CDROM
177 #include <vfs/vfs_implementation_cdrom.h>
178 #endif
179
180 #if (defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE - 0) >= 200112) || (defined(__POSIX_VISIBLE) && __POSIX_VISIBLE >= 200112) || (defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112) || __USE_LARGEFILE || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64)
181 #ifndef HAVE_64BIT_OFFSETS
182 #define HAVE_64BIT_OFFSETS
183 #endif
184 #endif
185
186 #define RFILE_HINT_UNBUFFERED (1 << 8)
187
188 int64_t retro_vfs_file_seek_internal(
189       libretro_vfs_implementation_file *stream,
190       int64_t offset, int whence)
191 {
192    if (!stream)
193       return -1;
194
195    if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
196    {
197 #ifdef HAVE_CDROM
198       if (stream->scheme == VFS_SCHEME_CDROM)
199          return retro_vfs_file_seek_cdrom(stream, offset, whence);
200 #endif
201 #ifdef ATLEAST_VC2005
202       /* VC2005 and up have a special 64-bit fseek */
203       return _fseeki64(stream->fp, offset, whence);
204 #elif defined(ORBIS)
205       {
206          int ret = orbisLseek(stream->fd, offset, whence);
207          if (ret < 0)
208             return -1;
209          return 0;
210       }
211 #elif defined(HAVE_64BIT_OFFSETS)
212       return fseeko(stream->fp, (off_t)offset, whence);
213 #else
214       return fseek(stream->fp, (long)offset, whence);
215 #endif
216    }
217 #ifdef HAVE_MMAP
218    /* Need to check stream->mapped because this function is
219     * called in filestream_open() */
220    if (stream->mapped && stream->hints &
221          RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
222    {
223       /* fseek() returns error on under/overflow but
224        * allows cursor > EOF for
225        read-only file descriptors. */
226       switch (whence)
227       {
228          case SEEK_SET:
229             if (offset < 0)
230                return -1;
231
232             stream->mappos = offset;
233             break;
234
235          case SEEK_CUR:
236             if (  (offset < 0 && stream->mappos + offset > stream->mappos) ||
237                   (offset > 0 && stream->mappos + offset < stream->mappos))
238                return -1;
239
240             stream->mappos += offset;
241             break;
242
243          case SEEK_END:
244             if (stream->mapsize + offset < stream->mapsize)
245                return -1;
246
247             stream->mappos = stream->mapsize + offset;
248             break;
249       }
250       return stream->mappos;
251    }
252 #endif
253
254    if (lseek(stream->fd, (off_t)offset, whence) < 0)
255       return -1;
256
257    return 0;
258 }
259
260 /**
261  * retro_vfs_file_open_impl:
262  * @path               : path to file
263  * @mode               : file mode to use when opening (read/write)
264  * @hints              :
265  *
266  * Opens a file for reading or writing, depending on the requested mode.
267  * Returns a pointer to an RFILE if opened successfully, otherwise NULL.
268  **/
269
270 libretro_vfs_implementation_file *retro_vfs_file_open_impl(
271       const char *path, unsigned mode, unsigned hints)
272 {
273 #if defined(VFS_FRONTEND) || defined(HAVE_CDROM)
274    int                             path_len = (int)strlen(path);
275 #endif
276 #ifdef VFS_FRONTEND
277    const char                 *dumb_prefix  = "vfsonly://";
278    size_t                   dumb_prefix_siz = STRLEN_CONST("vfsonly://");
279    int                      dumb_prefix_len = (int)dumb_prefix_siz;
280 #endif
281 #ifdef HAVE_CDROM
282    const char *cdrom_prefix                 = "cdrom://";
283    size_t cdrom_prefix_siz                  = STRLEN_CONST("cdrom://");
284    int cdrom_prefix_len                     = (int)cdrom_prefix_siz;
285 #endif
286    int                                flags = 0;
287    const char                     *mode_str = NULL;
288    libretro_vfs_implementation_file *stream = 
289       (libretro_vfs_implementation_file*)
290       malloc(sizeof(*stream));
291
292    if (!stream)
293       return NULL;
294
295    stream->fd                     = 0;
296    stream->hints                  = hints;
297    stream->size                   = 0;
298    stream->buf                    = NULL;
299    stream->fp                     = NULL;
300 #ifdef _WIN32
301    stream->fh                     = 0;
302 #endif
303    stream->orig_path              = NULL;
304    stream->mappos                 = 0;
305    stream->mapsize                = 0;
306    stream->mapped                 = NULL;
307    stream->scheme                 = VFS_SCHEME_NONE;
308
309 #ifdef VFS_FRONTEND
310    if (path_len >= dumb_prefix_len)
311       if (!memcmp(path, dumb_prefix, dumb_prefix_len))
312          path             += dumb_prefix_siz;
313 #endif
314
315 #ifdef HAVE_CDROM
316    stream->cdrom.cue_buf          = NULL;
317    stream->cdrom.cue_len          = 0;
318    stream->cdrom.byte_pos         = 0;
319    stream->cdrom.drive            = 0;
320    stream->cdrom.cur_min          = 0;
321    stream->cdrom.cur_sec          = 0;
322    stream->cdrom.cur_frame        = 0;
323    stream->cdrom.cur_track        = 0;
324    stream->cdrom.cur_lba          = 0;
325    stream->cdrom.last_frame_lba   = 0;
326    stream->cdrom.last_frame[0]    = '\0';
327    stream->cdrom.last_frame_valid = false;
328
329    if (path_len > cdrom_prefix_len)
330    {
331       if (!memcmp(path, cdrom_prefix, cdrom_prefix_len))
332       {
333          path             += cdrom_prefix_siz;
334          stream->scheme    = VFS_SCHEME_CDROM;
335       }
336    }
337 #endif
338
339    stream->orig_path       = strdup(path);
340
341 #ifdef HAVE_MMAP
342    if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS && mode == RETRO_VFS_FILE_ACCESS_READ)
343       stream->hints |= RFILE_HINT_UNBUFFERED;
344    else
345 #endif
346       stream->hints &= ~RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS;
347
348    switch (mode)
349    {
350       case RETRO_VFS_FILE_ACCESS_READ:
351          mode_str = "rb";
352
353          flags    = O_RDONLY;
354 #ifdef _WIN32
355          flags   |= O_BINARY;
356 #endif
357          break;
358
359       case RETRO_VFS_FILE_ACCESS_WRITE:
360          mode_str = "wb";
361
362          flags    = O_WRONLY | O_CREAT | O_TRUNC;
363 #if !defined(ORBIS)
364 #if !defined(_WIN32)
365          flags   |= S_IRUSR | S_IWUSR;
366 #else
367          flags   |= O_BINARY;
368 #endif
369 #endif
370          break;
371
372       case RETRO_VFS_FILE_ACCESS_READ_WRITE:
373          mode_str = "w+b";
374          flags    = O_RDWR | O_CREAT | O_TRUNC;
375 #if !defined(ORBIS)
376 #if !defined(_WIN32)
377          flags   |= S_IRUSR | S_IWUSR;
378 #else
379          flags   |= O_BINARY;
380 #endif
381 #endif
382          break;
383
384       case RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING:
385       case RETRO_VFS_FILE_ACCESS_READ_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING:
386          mode_str = "r+b";
387
388          flags    = O_RDWR;
389 #if !defined(ORBIS)
390 #if !defined(_WIN32)
391          flags   |= S_IRUSR | S_IWUSR;
392 #else
393          flags   |= O_BINARY;
394 #endif
395 #endif
396          break;
397
398       default:
399          goto error;
400    }
401
402    if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
403    {
404 #ifdef ORBIS
405       int fd = orbisOpen(path, flags, 0644);
406       if (fd < 0)
407       {
408          stream->fd = -1;
409          goto error;
410       }
411       stream->fd    = fd;
412 #else
413       FILE *fp;
414 #ifdef HAVE_CDROM
415       if (stream->scheme == VFS_SCHEME_CDROM)
416       {
417          retro_vfs_file_open_cdrom(stream, path, mode, hints);
418 #if defined(_WIN32) && !defined(_XBOX)
419          if (!stream->fh)
420             goto error;
421 #else
422          if (!stream->fp)
423             goto error;
424 #endif
425       }
426       else
427 #endif
428       {
429          fp = (FILE*)fopen_utf8(path, mode_str);
430
431          if (!fp)
432             goto error;
433
434          stream->fp  = fp;
435       }
436       /* Regarding setvbuf:
437        *
438        * https://www.freebsd.org/cgi/man.cgi?query=setvbuf&apropos=0&sektion=0&manpath=FreeBSD+11.1-RELEASE&arch=default&format=html
439        *
440        * If the size argument is not zero but buf is NULL, 
441        * a buffer of the given size will be allocated immediately, and
442        * released on close. This is an extension to ANSI C.
443        *
444        * Since C89 does not support specifying a NULL buffer 
445        * with a non-zero size, we create and track our own buffer for it.
446        */
447       /* TODO: this is only useful for a few platforms, 
448        * find which and add ifdef */
449 #if defined(_3DS)
450       if (stream->scheme != VFS_SCHEME_CDROM)
451       {
452          stream->buf = (char*)calloc(1, 0x10000);
453          if (stream->fp)
454             setvbuf(stream->fp, stream->buf, _IOFBF, 0x10000);
455       }
456 #elif !defined(PSP)
457       if (stream->scheme != VFS_SCHEME_CDROM)
458       {
459          stream->buf = (char*)calloc(1, 0x4000);
460          if (stream->fp)
461             setvbuf(stream->fp, stream->buf, _IOFBF, 0x4000);
462       }
463 #endif
464 #endif
465    }
466    else
467    {
468 #if defined(_WIN32) && !defined(_XBOX)
469 #if defined(LEGACY_WIN32)
470       char *path_local    = utf8_to_local_string_alloc(path);
471       stream->fd          = open(path_local, flags, 0);
472       if (path_local)
473          free(path_local);
474 #else
475       wchar_t * path_wide = utf8_to_utf16_string_alloc(path);
476       stream->fd          = _wopen(path_wide, flags, 0);
477       if (path_wide)
478          free(path_wide);
479 #endif
480 #else
481       stream->fd          = open(path, flags, 0);
482 #endif
483
484       if (stream->fd == -1)
485          goto error;
486
487 #ifdef HAVE_MMAP
488       if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
489       {
490          stream->mappos  = 0;
491          stream->mapped  = NULL;
492          stream->mapsize = retro_vfs_file_seek_internal(stream, 0, SEEK_END);
493
494          if (stream->mapsize == (uint64_t)-1)
495             goto error;
496
497          retro_vfs_file_seek_internal(stream, 0, SEEK_SET);
498
499          stream->mapped = (uint8_t*)mmap((void*)0,
500                stream->mapsize, PROT_READ,  MAP_SHARED, stream->fd, 0);
501
502          if (stream->mapped == MAP_FAILED)
503             stream->hints &= ~RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS;
504       }
505 #endif
506    }
507 #ifdef ORBIS
508    stream->size = orbisLseek(stream->fd, 0, SEEK_END);
509    orbisLseek(stream->fd, 0, SEEK_SET);
510 #else
511 #ifdef HAVE_CDROM
512    if (stream->scheme == VFS_SCHEME_CDROM)
513    {
514       retro_vfs_file_seek_cdrom(stream, 0, SEEK_SET);
515       retro_vfs_file_seek_cdrom(stream, 0, SEEK_END);
516
517       stream->size = retro_vfs_file_tell_impl(stream);
518
519       retro_vfs_file_seek_cdrom(stream, 0, SEEK_SET);
520    }
521    else
522 #endif
523    {
524       retro_vfs_file_seek_internal(stream, 0, SEEK_SET);
525       retro_vfs_file_seek_internal(stream, 0, SEEK_END);
526
527       stream->size = retro_vfs_file_tell_impl(stream);
528
529       retro_vfs_file_seek_internal(stream, 0, SEEK_SET);
530    }
531 #endif
532    return stream;
533
534 error:
535    retro_vfs_file_close_impl(stream);
536    return NULL;
537 }
538
539 int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream)
540 {
541    if (!stream)
542       return -1;
543
544 #ifdef HAVE_CDROM
545    if (stream->scheme == VFS_SCHEME_CDROM)
546    {
547       retro_vfs_file_close_cdrom(stream);
548       goto end;
549    }
550 #endif
551
552    if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
553    {
554       if (stream->fp)
555          fclose(stream->fp);
556    }
557    else
558    {
559 #ifdef HAVE_MMAP
560       if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
561          munmap(stream->mapped, stream->mapsize);
562 #endif
563    }
564
565    if (stream->fd > 0)
566    {
567 #ifdef ORBIS
568       orbisClose(stream->fd);
569       stream->fd = -1;
570 #else
571       close(stream->fd);
572 #endif
573    }
574 #ifdef HAVE_CDROM
575 end:
576    if (stream->cdrom.cue_buf)
577       free(stream->cdrom.cue_buf);
578 #endif
579    if (stream->buf)
580       free(stream->buf);
581
582    if (stream->orig_path)
583       free(stream->orig_path);
584
585    free(stream);
586
587    return 0;
588 }
589
590 int retro_vfs_file_error_impl(libretro_vfs_implementation_file *stream)
591 {
592 #ifdef HAVE_CDROM
593    if (stream->scheme == VFS_SCHEME_CDROM)
594       return retro_vfs_file_error_cdrom(stream);
595 #endif
596 #ifdef ORBIS
597    /* TODO/FIXME - implement this? */
598    return 0;
599 #else
600    return ferror(stream->fp);
601 #endif
602 }
603
604 int64_t retro_vfs_file_size_impl(libretro_vfs_implementation_file *stream)
605 {
606    if (stream)
607       return stream->size;
608    return 0;
609 }
610
611 int64_t retro_vfs_file_truncate_impl(libretro_vfs_implementation_file *stream, int64_t length)
612 {
613    if (!stream)
614       return -1;
615
616 #ifdef _WIN32
617    if (_chsize(_fileno(stream->fp), length) != 0)
618       return -1;
619 #elif !defined(VITA) && !defined(PSP) && !defined(PS2) && !defined(ORBIS) && (!defined(SWITCH) || defined(HAVE_LIBNX))
620    if (ftruncate(fileno(stream->fp), (off_t)length) != 0)
621       return -1;
622 #endif
623
624    return 0;
625 }
626
627 int64_t retro_vfs_file_tell_impl(libretro_vfs_implementation_file *stream)
628 {
629    if (!stream)
630       return -1;
631
632    if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
633    {
634 #ifdef HAVE_CDROM
635       if (stream->scheme == VFS_SCHEME_CDROM)
636          return retro_vfs_file_tell_cdrom(stream);
637 #endif
638 #ifdef ORBIS
639       {
640          int64_t ret = orbisLseek(stream->fd, 0, SEEK_CUR);
641          if (ret < 0)
642             return -1;
643          return ret;
644       }
645 #else
646 #ifdef ATLEAST_VC2005
647       /* VC2005 and up have a special 64-bit ftell */
648       return _ftelli64(stream->fp);
649 #elif defined(HAVE_64BIT_OFFSETS)
650       return ftello(stream->fp);
651 #else
652       return ftell(stream->fp);
653 #endif
654 #endif
655    }
656 #ifdef HAVE_MMAP
657    /* Need to check stream->mapped because this function
658     * is called in filestream_open() */
659    if (stream->mapped && stream->hints & 
660          RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
661       return stream->mappos;
662 #endif
663    if (lseek(stream->fd, 0, SEEK_CUR) < 0)
664       return -1;
665
666    return 0;
667 }
668
669 int64_t retro_vfs_file_seek_impl(libretro_vfs_implementation_file *stream,
670       int64_t offset, int seek_position)
671 {
672    int whence = -1;
673    switch (seek_position)
674    {
675       case RETRO_VFS_SEEK_POSITION_START:
676          whence = SEEK_SET;
677          break;
678       case RETRO_VFS_SEEK_POSITION_CURRENT:
679          whence = SEEK_CUR;
680          break;
681       case RETRO_VFS_SEEK_POSITION_END:
682          whence = SEEK_END;
683          break;
684    }
685
686    return retro_vfs_file_seek_internal(stream, offset, whence);
687 }
688
689 int64_t retro_vfs_file_read_impl(libretro_vfs_implementation_file *stream,
690       void *s, uint64_t len)
691 {
692    if (!stream || !s)
693       return -1;
694
695    if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
696    {
697 #ifdef HAVE_CDROM
698       if (stream->scheme == VFS_SCHEME_CDROM)
699          return retro_vfs_file_read_cdrom(stream, s, len);
700 #endif
701 #ifdef ORBIS
702       if (orbisRead(stream->fd, s, (size_t)len) < 0)
703          return -1;
704       return 0;
705 #else
706       return fread(s, 1, (size_t)len, stream->fp);
707 #endif
708    }
709 #ifdef HAVE_MMAP
710    if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
711    {
712       if (stream->mappos > stream->mapsize)
713          return -1;
714
715       if (stream->mappos + len > stream->mapsize)
716          len = stream->mapsize - stream->mappos;
717
718       memcpy(s, &stream->mapped[stream->mappos], len);
719       stream->mappos += len;
720
721       return len;
722    }
723 #endif
724
725    return read(stream->fd, s, (size_t)len);
726 }
727
728 int64_t retro_vfs_file_write_impl(libretro_vfs_implementation_file *stream, const void *s, uint64_t len)
729 {
730    if (!stream)
731       return -1;
732
733    if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
734    {
735 #ifdef ORBIS
736       if (orbisWrite(stream->fd, s, (size_t)len) < 0)
737          return -1;
738       return 0;
739 #else
740       return fwrite(s, 1, (size_t)len, stream->fp);
741 #endif
742    }
743
744 #ifdef HAVE_MMAP
745    if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
746       return -1;
747 #endif
748    return write(stream->fd, s, (size_t)len);
749 }
750
751 int retro_vfs_file_flush_impl(libretro_vfs_implementation_file *stream)
752 {
753    if (!stream)
754       return -1;
755 #ifdef ORBIS
756    return 0;
757 #else
758    return fflush(stream->fp) == 0 ? 0 : -1;
759 #endif
760 }
761
762 int retro_vfs_file_remove_impl(const char *path)
763 {
764 #if defined(_WIN32) && !defined(_XBOX)
765    /* Win32 (no Xbox) */
766
767 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500
768    char *path_local    = NULL;
769 #else
770    wchar_t *path_wide  = NULL;
771 #endif
772    if (!path || !*path)
773       return -1;
774 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500
775    path_local = utf8_to_local_string_alloc(path);
776
777    if (path_local)
778    {
779       int ret = remove(path_local);
780       free(path_local);
781
782       if (ret == 0)
783          return 0;
784    }
785 #else
786    path_wide = utf8_to_utf16_string_alloc(path);
787
788    if (path_wide)
789    {
790       int ret = _wremove(path_wide);
791       free(path_wide);
792
793       if (ret == 0)
794          return 0;
795    }
796 #endif
797    return -1;
798 #elif defined(ORBIS)
799    /* Orbis
800     * TODO/FIXME - stub for now */
801    return 0;
802 #else
803    if (remove(path) == 0)
804       return 0;
805    return -1;
806 #endif
807 }
808
809 int retro_vfs_file_rename_impl(const char *old_path, const char *new_path)
810 {
811 #if defined(_WIN32) && !defined(_XBOX)
812    /* Win32 (no Xbox) */
813    int ret                 = -1;
814 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500
815    char *old_path_local    = NULL;
816 #else
817    wchar_t *old_path_wide  = NULL;
818 #endif
819
820    if (!old_path || !*old_path || !new_path || !*new_path)
821       return -1;
822
823 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500
824    old_path_local = utf8_to_local_string_alloc(old_path);
825
826    if (old_path_local)
827    {
828       char *new_path_local = utf8_to_local_string_alloc(new_path);
829
830       if (new_path_local)
831       {
832          if (rename(old_path_local, new_path_local) == 0)
833             ret = 0;
834          free(new_path_local);
835       }
836
837       free(old_path_local);
838    }
839 #else
840    old_path_wide = utf8_to_utf16_string_alloc(old_path);
841
842    if (old_path_wide)
843    {
844       wchar_t *new_path_wide = utf8_to_utf16_string_alloc(new_path);
845
846       if (new_path_wide)
847       {
848          if (_wrename(old_path_wide, new_path_wide) == 0)
849             ret = 0;
850          free(new_path_wide);
851       }
852
853       free(old_path_wide);
854    }
855 #endif
856    return ret;
857
858 #elif defined(ORBIS)
859    /* Orbis */
860    /* TODO/FIXME - Stub for now */
861    if (!old_path || !*old_path || !new_path || !*new_path)
862       return -1;
863    return 0;
864
865 #else
866    /* Every other platform */
867    if (!old_path || !*old_path || !new_path || !*new_path)
868       return -1;
869    return rename(old_path, new_path) == 0 ? 0 : -1;
870 #endif
871 }
872
873 const char *retro_vfs_file_get_path_impl(
874       libretro_vfs_implementation_file *stream)
875 {
876    /* should never happen, do something noisy so caller can be fixed */
877    if (!stream)
878       abort();
879    return stream->orig_path;
880 }
881
882 int retro_vfs_stat_impl(const char *path, int32_t *size)
883 {
884    bool is_dir               = false;
885    bool is_character_special = false;
886 #if defined(VITA) || defined(PSP)
887    /* Vita / PSP */
888    SceIoStat buf;
889    int dir_ret;
890    char *tmp                 = NULL;
891    size_t len                = 0;
892
893    if (!path || !*path)
894       return 0;
895
896    tmp                       = strdup(path);
897    len                       = strlen(tmp);
898    if (tmp[len-1] == '/')
899       tmp[len-1] = '\0';
900
901    dir_ret                   = sceIoGetstat(tmp, &buf);
902    free(tmp);
903    if (dir_ret < 0)
904       return 0;
905
906    if (size)
907       *size                  = (int32_t)buf.st_size;
908
909    is_dir                    = FIO_S_ISDIR(buf.st_mode);
910 #elif defined(ORBIS)
911    /* Orbis */
912    int dir_ret               = 0;
913
914    if (!path || !*path)
915       return 0;
916
917    if (size)
918       *size                  = (int32_t)buf.st_size;
919
920    dir_ret                   = orbisDopen(path);
921    is_dir                    = dir_ret > 0;
922    orbisDclose(dir_ret);
923
924    is_character_special      = S_ISCHR(buf.st_mode);
925 #elif defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
926    /* CellOS Lv2 */
927    CellFsStat buf;
928
929    if (!path || !*path)
930       return 0;
931    if (cellFsStat(path, &buf) < 0)
932       return 0;
933
934    if (size)
935       *size                  = (int32_t)buf.st_size;
936
937    is_dir                    = ((buf.st_mode & S_IFMT) == S_IFDIR);
938
939 #elif defined(_WIN32)
940    /* Windows */
941    DWORD file_info;
942    struct _stat buf;
943 #if defined(LEGACY_WIN32)
944    char *path_local          = NULL;
945 #else
946    wchar_t *path_wide        = NULL;
947 #endif
948
949    if (!path || !*path)
950       return 0;
951
952 #if defined(LEGACY_WIN32)
953    path_local                = utf8_to_local_string_alloc(path);
954    file_info                 = GetFileAttributes(path_local);
955
956    if (!string_is_empty(path_local))
957       _stat(path_local, &buf);
958
959    if (path_local)
960       free(path_local);
961 #else
962    path_wide                 = utf8_to_utf16_string_alloc(path);
963    file_info                 = GetFileAttributesW(path_wide);
964
965    _wstat(path_wide, &buf);
966
967    if (path_wide)
968       free(path_wide);
969 #endif
970
971    if (file_info == INVALID_FILE_ATTRIBUTES)
972       return 0;
973
974    if (size)
975       *size = (int32_t)buf.st_size;
976
977    is_dir = (file_info & FILE_ATTRIBUTE_DIRECTORY);
978
979 #elif defined(GEKKO)
980    /* On GEKKO platforms, paths cannot have
981     * trailing slashes - we must therefore
982     * remove them */
983    char *path_buf = NULL;
984    int stat_ret   = -1;
985    struct stat stat_buf;
986    size_t len;
987
988    if (string_is_empty(path))
989       return 0;
990
991    path_buf = strdup(path);
992    if (!path_buf)
993       return 0;
994
995    len = strlen(path_buf);
996    if (len > 0)
997       if (path_buf[len - 1] == '/')
998          path_buf[len - 1] = '\0';
999
1000    stat_ret = stat(path_buf, &stat_buf);
1001    free(path_buf);
1002
1003    if (stat_ret < 0)
1004       return 0;
1005
1006    if (size)
1007       *size             = (int32_t)stat_buf.st_size;
1008
1009    is_dir               = S_ISDIR(stat_buf.st_mode);
1010    is_character_special = S_ISCHR(stat_buf.st_mode);
1011
1012 #else
1013    /* Every other platform */
1014    struct stat buf;
1015
1016    if (!path || !*path)
1017       return 0;
1018    if (stat(path, &buf) < 0)
1019       return 0;
1020
1021    if (size)
1022       *size             = (int32_t)buf.st_size;
1023
1024    is_dir               = S_ISDIR(buf.st_mode);
1025    is_character_special = S_ISCHR(buf.st_mode);
1026 #endif
1027    return RETRO_VFS_STAT_IS_VALID | (is_dir ? RETRO_VFS_STAT_IS_DIRECTORY : 0) | (is_character_special ? RETRO_VFS_STAT_IS_CHARACTER_SPECIAL : 0);
1028 }
1029
1030 #if defined(VITA)
1031 #define path_mkdir_error(ret) (((ret) == SCE_ERROR_ERRNO_EEXIST))
1032 #elif defined(PSP) || defined(PS2) || defined(_3DS) || defined(WIIU) || defined(SWITCH) || defined(ORBIS)
1033 #define path_mkdir_error(ret) ((ret) == -1)
1034 #else
1035 #define path_mkdir_error(ret) ((ret) < 0 && errno == EEXIST)
1036 #endif
1037
1038 int retro_vfs_mkdir_impl(const char *dir)
1039 {
1040 #if defined(_WIN32)
1041 #ifdef LEGACY_WIN32
1042    int ret        = _mkdir(dir);
1043 #else
1044    wchar_t *dir_w = utf8_to_utf16_string_alloc(dir);
1045    int       ret  = -1;
1046
1047    if (dir_w)
1048    {
1049       ret = _wmkdir(dir_w);
1050       free(dir_w);
1051    }
1052 #endif
1053 #elif defined(IOS)
1054    int ret = mkdir(dir, 0755);
1055 #elif defined(VITA) || defined(PSP)
1056    int ret = sceIoMkdir(dir, 0777);
1057 #elif defined(ORBIS)
1058    int ret = orbisMkdir(dir, 0755);
1059 #elif defined(__QNX__)
1060    int ret = mkdir(dir, 0777);
1061 #elif defined(GEKKO)
1062    /* On GEKKO platforms, mkdir() fails if
1063     * the path has a trailing slash. We must
1064     * therefore remove it. */
1065    int ret = -1;
1066    if (!string_is_empty(dir))
1067    {
1068       char *dir_buf = strdup(dir);
1069
1070       if (dir_buf)
1071       {
1072          size_t len = strlen(dir_buf);
1073
1074          if (len > 0)
1075             if (dir_buf[len - 1] == '/')
1076                dir_buf[len - 1] = '\0';
1077
1078          ret = mkdir(dir_buf, 0750);
1079
1080          free(dir_buf);
1081       }
1082    }
1083 #else
1084    int ret = mkdir(dir, 0750);
1085 #endif
1086
1087    if (path_mkdir_error(ret))
1088       return -2;
1089    return ret < 0 ? -1 : 0;
1090 }
1091
1092 #ifdef VFS_FRONTEND
1093 struct retro_vfs_dir_handle
1094 #else
1095 struct libretro_vfs_implementation_dir
1096 #endif
1097 {
1098    char* orig_path;
1099 #if defined(_WIN32)
1100 #if defined(LEGACY_WIN32)
1101    WIN32_FIND_DATA entry;
1102 #else
1103    WIN32_FIND_DATAW entry;
1104 #endif
1105    HANDLE directory;
1106    bool next;
1107    char path[PATH_MAX_LENGTH];
1108 #elif defined(VITA) || defined(PSP)
1109    SceUID directory;
1110    SceIoDirent entry;
1111 #elif defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
1112    CellFsErrno error;
1113    int directory;
1114    CellFsDirent entry;
1115 #elif defined(ORBIS)
1116    int directory;
1117    struct dirent entry;
1118 #else
1119    DIR *directory;
1120    const struct dirent *entry;
1121 #endif
1122 };
1123
1124 static bool dirent_check_error(libretro_vfs_implementation_dir *rdir)
1125 {
1126 #if defined(_WIN32)
1127    return (rdir->directory == INVALID_HANDLE_VALUE);
1128 #elif defined(VITA) || defined(PSP) || defined(ORBIS)
1129    return (rdir->directory < 0);
1130 #elif defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
1131    return (rdir->error != CELL_FS_SUCCEEDED);
1132 #else
1133    return !(rdir->directory);
1134 #endif
1135 }
1136
1137 libretro_vfs_implementation_dir *retro_vfs_opendir_impl(
1138       const char *name, bool include_hidden)
1139 {
1140 #if defined(_WIN32)
1141    unsigned path_len;
1142    char path_buf[1024];
1143    size_t copied      = 0;
1144 #if defined(LEGACY_WIN32)
1145    char *path_local   = NULL;
1146 #else
1147    wchar_t *path_wide = NULL;
1148 #endif
1149 #endif
1150    libretro_vfs_implementation_dir *rdir;
1151
1152    /*Reject null or empty string paths*/
1153    if (!name || (*name == 0))
1154       return NULL;
1155
1156    /*Allocate RDIR struct. Tidied later with retro_closedir*/
1157    rdir = (libretro_vfs_implementation_dir*)calloc(1, sizeof(*rdir));
1158    if (!rdir)
1159       return NULL;
1160
1161    rdir->orig_path       = strdup(name);
1162
1163 #if defined(_WIN32)
1164    path_buf[0]           = '\0';
1165    path_len              = strlen(name);
1166
1167    copied                = strlcpy(path_buf, name, sizeof(path_buf));
1168
1169    /* Non-NT platforms don't like extra slashes in the path */
1170    if (name[path_len - 1] != '\\')
1171       path_buf[copied++]   = '\\';
1172
1173    path_buf[copied]        = '*';
1174    path_buf[copied+1]      = '\0';
1175
1176 #if defined(LEGACY_WIN32)
1177    path_local              = utf8_to_local_string_alloc(path_buf);
1178    rdir->directory         = FindFirstFile(path_local, &rdir->entry);
1179
1180    if (path_local)
1181       free(path_local);
1182 #else
1183    path_wide               = utf8_to_utf16_string_alloc(path_buf);
1184    rdir->directory         = FindFirstFileW(path_wide, &rdir->entry);
1185
1186    if (path_wide)
1187       free(path_wide);
1188 #endif
1189
1190 #elif defined(VITA) || defined(PSP)
1191    rdir->directory       = sceIoDopen(name);
1192 #elif defined(_3DS)
1193    rdir->directory       = !string_is_empty(name) ? opendir(name) : NULL;
1194    rdir->entry           = NULL;
1195 #elif defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
1196    rdir->error           = cellFsOpendir(name, &rdir->directory);
1197 #elif defined(ORBIS)
1198    rdir->directory       = orbisDopen(name);
1199 #else
1200    rdir->directory       = opendir(name);
1201    rdir->entry           = NULL;
1202 #endif
1203
1204 #ifdef _WIN32
1205    if (include_hidden)
1206       rdir->entry.dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;
1207    else
1208       rdir->entry.dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN;
1209 #endif
1210
1211    if (rdir->directory && !dirent_check_error(rdir))
1212       return rdir;
1213
1214    retro_vfs_closedir_impl(rdir);
1215    return NULL;
1216 }
1217
1218 bool retro_vfs_readdir_impl(libretro_vfs_implementation_dir *rdir)
1219 {
1220 #if defined(_WIN32)
1221    if (rdir->next)
1222 #if defined(LEGACY_WIN32)
1223       return (FindNextFile(rdir->directory, &rdir->entry) != 0);
1224 #else
1225       return (FindNextFileW(rdir->directory, &rdir->entry) != 0);
1226 #endif
1227
1228    rdir->next = true;
1229    return (rdir->directory != INVALID_HANDLE_VALUE);
1230 #elif defined(VITA) || defined(PSP)
1231    return (sceIoDread(rdir->directory, &rdir->entry) > 0);
1232 #elif defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
1233    uint64_t nread;
1234    rdir->error = cellFsReaddir(rdir->directory, &rdir->entry, &nread);
1235    return (nread != 0);
1236 #elif defined(ORBIS)
1237    return (orbisDread(rdir->directory, &rdir->entry) > 0);
1238 #else
1239    return ((rdir->entry = readdir(rdir->directory)) != NULL);
1240 #endif
1241 }
1242
1243 const char *retro_vfs_dirent_get_name_impl(libretro_vfs_implementation_dir *rdir)
1244 {
1245 #if defined(_WIN32)
1246 #if defined(LEGACY_WIN32)
1247    char *name       = local_to_utf8_string_alloc(rdir->entry.cFileName);
1248 #else
1249    char *name       = utf16_to_utf8_string_alloc(rdir->entry.cFileName);
1250 #endif
1251    memset(rdir->entry.cFileName, 0, sizeof(rdir->entry.cFileName));
1252    strlcpy((char*)rdir->entry.cFileName, name, sizeof(rdir->entry.cFileName));
1253    if (name)
1254       free(name);
1255    return (char*)rdir->entry.cFileName;
1256 #elif defined(VITA) || defined(PSP) || defined(__CELLOS_LV2__) && !defined(__PSL1GHT__) || defined(ORBIS)
1257    return rdir->entry.d_name;
1258 #else
1259    if (!rdir || !rdir->entry)
1260       return NULL;
1261    return rdir->entry->d_name;
1262 #endif
1263 }
1264
1265 bool retro_vfs_dirent_is_dir_impl(libretro_vfs_implementation_dir *rdir)
1266 {
1267 #if defined(_WIN32)
1268    const WIN32_FIND_DATA *entry = (const WIN32_FIND_DATA*)&rdir->entry;
1269    return entry->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
1270 #elif defined(PSP) || defined(VITA)
1271    const SceIoDirent *entry     = (const SceIoDirent*)&rdir->entry;
1272 #if defined(PSP)
1273    return (entry->d_stat.st_attr & FIO_SO_IFDIR) == FIO_SO_IFDIR;
1274 #elif defined(VITA)
1275    return SCE_S_ISDIR(entry->d_stat.st_mode);
1276 #endif
1277 #elif defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
1278    CellFsDirent *entry          = (CellFsDirent*)&rdir->entry;
1279    return (entry->d_type == CELL_FS_TYPE_DIRECTORY);
1280 #elif defined(ORBIS)
1281    const struct dirent *entry   = &rdir->entry;
1282    if (entry->d_type == DT_DIR)
1283       return true;
1284    if (!(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK))
1285       return false;
1286 #else
1287    struct stat buf;
1288    char path[PATH_MAX_LENGTH];
1289 #if defined(DT_DIR)
1290    const struct dirent *entry = (const struct dirent*)rdir->entry;
1291    if (entry->d_type == DT_DIR)
1292       return true;
1293    /* This can happen on certain file systems. */
1294    if (!(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK))
1295       return false;
1296 #endif
1297    /* dirent struct doesn't have d_type, do it the slow way ... */
1298    path[0] = '\0';
1299    fill_pathname_join(path, rdir->orig_path, retro_vfs_dirent_get_name_impl(rdir), sizeof(path));
1300    if (stat(path, &buf) < 0)
1301       return false;
1302    return S_ISDIR(buf.st_mode);
1303 #endif
1304 }
1305
1306 int retro_vfs_closedir_impl(libretro_vfs_implementation_dir *rdir)
1307 {
1308    if (!rdir)
1309       return -1;
1310
1311 #if defined(_WIN32)
1312    if (rdir->directory != INVALID_HANDLE_VALUE)
1313       FindClose(rdir->directory);
1314 #elif defined(VITA) || defined(PSP)
1315    sceIoDclose(rdir->directory);
1316 #elif defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
1317    rdir->error = cellFsClosedir(rdir->directory);
1318 #elif defined(ORBIS)
1319    orbisDclose(rdir->directory);
1320 #else
1321    if (rdir->directory)
1322       closedir(rdir->directory);
1323 #endif
1324
1325    if (rdir->orig_path)
1326       free(rdir->orig_path);
1327    free(rdir);
1328    return 0;
1329 }