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