Add libretro VFS and use VFS for windows target
[pcsx_rearmed.git] / libretro-common / vfs / vfs_implementation.c
CommitLineData
226a5691 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
188int64_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
270libretro_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
534error:
535 retro_vfs_file_close_impl(stream);
536 return NULL;
537}
538
539int 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
575end:
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
590int 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
604int64_t retro_vfs_file_size_impl(libretro_vfs_implementation_file *stream)
605{
606 if (stream)
607 return stream->size;
608 return 0;
609}
610
611int64_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
627int64_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
669int64_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
689int64_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
728int64_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
751int 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
762int 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
809int 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
873const 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
882int 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
1038int 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
1093struct retro_vfs_dir_handle
1094#else
1095struct 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
1124static 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
1137libretro_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
1218bool 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
1243const 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
1265bool 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
1306int 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}