Add libretro VFS and use VFS for windows target
authornegativeExponent <negativeExponent@users.noreply.github.com>
Wed, 21 Apr 2021 03:05:01 +0000 (11:05 +0800)
committernegativeExponent <negativeExponent@users.noreply.github.com>
Sat, 1 May 2021 20:38:43 +0000 (04:38 +0800)
- Reverts the use of fopen_utf8 from previous commit and use
  "file_stream_transforms.h" instead: \7fhttps://github.com/libretro/pcsx_rearmed/commit/270c6dd14f876b8a67929aa22abefc47f4588324#diff-2065d1414bac1198fb423574b4874d5bf92a2114a2f3e235c469d8fd6288dd10

- Enable the use of VFS for windows by default.

- compile with USE_LIBRETRO_VFS=1 to enable vfs when compiling for other
  platforms (including android if needed)

30 files changed:
Makefile
Makefile.libretro
deps/libchdr/include/libchdr/coretypes.h
frontend/libretro.c
jni/Android.mk
libpcsxcore/cdriso.c
libpcsxcore/psxmem.c
libpcsxcore/sio.c
libretro-common/compat/compat_posix_string.c [new file with mode: 0644]
libretro-common/compat/compat_strcasestr.c [new file with mode: 0644]
libretro-common/file/file_path.c [new file with mode: 0644]
libretro-common/include/compat/posix_string.h [new file with mode: 0644]
libretro-common/include/compat/strcasestr.h [new file with mode: 0644]
libretro-common/include/file/file_path.h [new file with mode: 0644]
libretro-common/include/memmap.h [new file with mode: 0644]
libretro-common/include/retro_assert.h [new file with mode: 0644]
libretro-common/include/retro_environment.h [new file with mode: 0644]
libretro-common/include/retro_miscellaneous.h [new file with mode: 0644]
libretro-common/include/streams/file_stream.h [new file with mode: 0644]
libretro-common/include/streams/file_stream_transforms.h [new file with mode: 0644]
libretro-common/include/string/stdstring.h [new file with mode: 0644]
libretro-common/include/time/rtime.h [new file with mode: 0644]
libretro-common/include/vfs/vfs.h [new file with mode: 0644]
libretro-common/include/vfs/vfs_implementation.h [new file with mode: 0644]
libretro-common/streams/file_stream.c [new file with mode: 0644]
libretro-common/streams/file_stream_transforms.c [new file with mode: 0644]
libretro-common/string/stdstring.c [new file with mode: 0644]
libretro-common/time/rtime.c [new file with mode: 0644]
libretro-common/vfs/vfs_implementation.c [new file with mode: 0644]
plugins/cdrcimg/cdrcimg.c

index 3d2d94e..3d71826 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -291,9 +291,19 @@ CFLAGS += `pkg-config --cflags glib-2.0 libosso dbus-1 hildon-fm-2`
 LDFLAGS += `pkg-config --libs glib-2.0 libosso dbus-1 hildon-fm-2`
 endif
 ifeq "$(PLATFORM)" "libretro"
+ifeq "$(USE_LIBRETRO_VFS)" "1"
+OBJS += libretro-common/compat/compat_posix_string.o
 OBJS += libretro-common/compat/fopen_utf8.o
 OBJS += libretro-common/encodings/compat_strl.o
 OBJS += libretro-common/encodings/encoding_utf.o
+OBJS += libretro-common/file/file_path.o
+OBJS += libretro-common/streams/file_stream.o
+OBJS += libretro-common/streams/file_stream_transforms.o
+OBJS += libretro-common/string/stdstring.o
+OBJS += libretro-common/time/rtime.o
+OBJS += libretro-common/vfs/vfs_implementation.o
+CFLAGS += -DUSE_LIBRETRO_VFS
+endif
 OBJS += frontend/libretro.o
 CFLAGS += -Ilibretro-common/include
 CFLAGS += -DFRONTEND_SUPPORTS_RGB565
index bd74dec..1ecd359 100644 (file)
@@ -3,6 +3,7 @@
 DEBUG ?= 0
 WANT_ZLIB ?= 1
 HAVE_CHD ?= 1
+USE_LIBRETRO_VFS ?= 0
 
 # Dynarec options: lightrec, ari64
 DYNAREC ?= lightrec
@@ -451,6 +452,7 @@ else
        LIBPTHREAD :=
        LIBDL :=
        LIBM :=
+       USE_LIBRETRO_VFS = 1
 endif
 
 CFLAGS += $(fpic)
index fc3f1d1..ceffa6b 100644 (file)
@@ -20,13 +20,16 @@ typedef int32_t INT32;
 typedef int16_t INT16;
 typedef int8_t INT8;
 
-#define core_file FILE
-#ifdef HAVE_LIBRETRO
-#include <compat/fopen_utf8.h>
-#define core_fopen(file) fopen_utf8(file, "rb")
+#ifdef USE_LIBRETRO_VFS
+#define core_file RFILE
+#define core_fopen(file) rfopen(file, "rb")
+#define core_fseek rfseek
+#define core_ftell rftell
+#define core_fread(fc, buff, len) rfread(buff, 1, len, fc)
+#define core_fclose rfclose
 #else
+#define core_file FILE
 #define core_fopen(file) fopen(file, "rb")
-#endif
 #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) || defined(__WIN64__)
        #define core_fseek _fseeki64
        #define core_ftell _ftelli64
@@ -39,6 +42,7 @@ typedef int8_t INT8;
 #endif
 #define core_fread(fc, buff, len) fread(buff, 1, len, fc)
 #define core_fclose fclose
+#endif
 
 static UINT64 core_fsize(core_file *f)
 {
index 07601cb..395bc13 100644 (file)
 #include "revision.h"
 
 #include <libretro.h>
-#include <compat/fopen_utf8.h>
 #include "libretro_core_options.h"
 
+#ifdef USE_LIBRETRO_VFS
+#include <streams/file_stream_transforms.h>
+#endif
+
 #ifdef _3DS
 #include "3ds/3ds_utils.h"
 #endif
@@ -556,6 +559,10 @@ static const struct retro_controller_info ports[9] =
 /* libretro */
 void retro_set_environment(retro_environment_t cb)
 {
+#ifdef USE_LIBRETRO_VFS
+   struct retro_vfs_interface_info vfs_iface_info;
+#endif
+
    environ_cb = cb;
 
    if (cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &logging))
@@ -563,6 +570,13 @@ void retro_set_environment(retro_environment_t cb)
 
    environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports);
    libretro_set_core_options(environ_cb);
+
+#ifdef USE_LIBRETRO_VFS
+   vfs_iface_info.required_interface_version = 1;
+   vfs_iface_info.iface                      = NULL;
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &vfs_iface_info))
+          filestream_vfs_init(&vfs_iface_info);
+#endif
 }
 
 void retro_set_video_refresh(retro_video_refresh_t cb) { video_cb = cb; }
@@ -1098,11 +1112,11 @@ static bool read_m3u(const char *file)
 {
    char line[1024];
    char name[PATH_MAX];
-   FILE *f = fopen_utf8(file, "r");
-   if (!f)
+   FILE *fp = fopen(file, "r");
+   if (!fp)
       return false;
 
-   while (fgets(line, sizeof(line), f) && disk_count < sizeof(disks) / sizeof(disks[0]))
+   while (fgets(fp, line, sizeof(line)) && disk_count < sizeof(disks) / sizeof(disks[0]))
    {
       if (line[0] == '#')
          continue;
@@ -1128,7 +1142,7 @@ static bool read_m3u(const char *file)
       }
    }
 
-   fclose(f);
+   fclose(fp);
    return (disk_count != 0);
 }
 
@@ -2554,17 +2568,15 @@ void retro_run(void)
 
 static bool try_use_bios(const char *path)
 {
-   FILE *f;
    long size;
    const char *name;
-
-   f = fopen_utf8(path, "rb");
-   if (f == NULL)
+   FILE *fp = fopen(path, "rb");
+   if (fp == NULL)
       return false;
 
-   fseek(f, 0, SEEK_END);
-   size = ftell(f);
-   fclose(f);
+   fseek(fp, 0, SEEK_END);
+   size = ftell(fp);
+   fclose(fp);
 
    if (size != 512 * 1024)
       return false;
index 4e828f1..40ebb1c 100644 (file)
@@ -5,6 +5,7 @@ $(shell cd "$(LOCAL_PATH)" && (diff -q ../frontend/revision.h_ ../frontend/revis
 $(shell cd "$(LOCAL_PATH)" && (rm ../frontend/revision.h_))
 
 HAVE_CHD ?= 1
+USE_LIBRETRO_VFS ?= 0
 
 ROOT_DIR     := $(LOCAL_PATH)/..
 CORE_DIR     := $(ROOT_DIR)/libpcsxcore
@@ -95,6 +96,21 @@ SOURCES_ASM :=
 COREFLAGS := -ffast-math -funroll-loops -DHAVE_LIBRETRO -DNO_FRONTEND -DFRONTEND_SUPPORTS_RGB565 -DANDROID -DREARMED
 COREFLAGS += -DHAVE_CHD -D_7ZIP_ST
 
+ifeq ($(USE_LIBRETRO_VFS),1)
+SOURCES_C += \
+             $(LIBRETRO_COMMON)/compat/compat_posix_string.c \
+             $(LIBRETRO_COMMON)/compat/fopen_utf8.c \
+             $(LIBRETRO_COMMON)/encodings/compat_strl.c \
+             $(LIBRETRO_COMMON)/encodings/encoding_utf.c \
+             $(LIBRETRO_COMMON)/file/file_path.c \
+             $(LIBRETRO_COMMON)/streams/file_stream.c \
+             $(LIBRETRO_COMMON)/streams/file_stream_transforms.c \
+             $(LIBRETRO_COMMON)/string/stdstring.c \
+             $(LIBRETRO_COMMON)/time/rtime.c \
+             $(LIBRETRO_COMMON)/vfs/vfs_implementation.c
+COREFLAGS += -DUSE_LIBRETRO_VFS
+endif
+
 HAVE_ARI64=0
 HAVE_LIGHTREC=0
 ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
index c36c196..fc1d077 100644 (file)
@@ -19,8 +19,6 @@
  *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.           *
  ***************************************************************************/
 
-#include <compat/fopen_utf8.h>
-
 #include "psxcommon.h"
 #include "plugins.h"
 #include "cdrom.h"
 #include <unistd.h>
 #endif
 
+#ifdef USE_LIBRETRO_VFS
+#include <streams/file_stream_transforms.h>
+#undef fseeko
+#undef ftello
+#define ftello rftell
+#define fseeko rfseek
+#endif
 #define OFF_T_MSB ((off_t)1 << (sizeof(off_t) * 8 - 1))
 
 unsigned int cdrIsoMultidiskCount;
@@ -336,16 +341,16 @@ static int parsetoc(const char *isofile) {
                return -1;
        }
 
-       if ((fi = fopen_utf8(tocname, "r")) == NULL) {
+       if ((fi = fopen(tocname, "r")) == NULL) {
                // try changing extension to .cue (to satisfy some stupid tutorials)
                strcpy(tocname + strlen(tocname) - 4, ".cue");
-               if ((fi = fopen_utf8(tocname, "r")) == NULL) {
+               if ((fi = fopen(tocname, "r")) == NULL) {
                        // if filename is image.toc.bin, try removing .bin (for Brasero)
                        strcpy(tocname, isofile);
                        t = strlen(tocname);
                        if (t >= 8 && strcmp(tocname + t - 8, ".toc.bin") == 0) {
                                tocname[t - 4] = '\0';
-                               if ((fi = fopen_utf8(tocname, "r")) == NULL) {
+                               if ((fi = fopen(tocname, "r")) == NULL) {
                                        return -1;
                                }
                        }
@@ -488,7 +493,7 @@ static int parsecue(const char *isofile) {
                return -1;
        }
 
-       if ((fi = fopen_utf8(cuename, "r")) == NULL) {
+       if ((fi = fopen(cuename, "r")) == NULL) {
                return -1;
        }
 
@@ -592,7 +597,7 @@ static int parsecue(const char *isofile) {
                        else
                                tmp = tmpb;
                        strncpy(incue_fname, tmp, incue_max_len);
-                       ti[numtracks + 1].handle = fopen_utf8(filepath, "rb");
+                       ti[numtracks + 1].handle = fopen(filepath, "rb");
 
                        // update global offset if this is not first file in this .cue
                        if (numtracks + 1 > 1) {
@@ -613,7 +618,7 @@ static int parsecue(const char *isofile) {
                                strncasecmp(isofile + strlen(isofile) - 4, ".cd", 3) == 0)) {
                                // user selected .cue/.cdX as image file, use it's data track instead
                                fclose(cdHandle);
-                               cdHandle = fopen_utf8(filepath, "rb");
+                               cdHandle = fopen(filepath, "rb");
                        }
                }
        }
@@ -647,7 +652,7 @@ static int parseccd(const char *isofile) {
                return -1;
        }
 
-       if ((fi = fopen_utf8(ccdname, "r")) == NULL) {
+       if ((fi = fopen(ccdname, "r")) == NULL) {
                return -1;
        }
 
@@ -706,7 +711,7 @@ static int parsemds(const char *isofile) {
                return -1;
        }
 
-       if ((fi = fopen_utf8(mdsname, "rb")) == NULL) {
+       if ((fi = fopen(mdsname, "rb")) == NULL) {
                return -1;
        }
 
@@ -1146,7 +1151,7 @@ static int opensubfile(const char *isoname) {
                return -1;
        }
 
-       subHandle = fopen_utf8(subname, "rb");
+       subHandle = fopen(subname, "rb");
        if (subHandle == NULL) {
                return -1;
        }
@@ -1625,7 +1630,7 @@ static long CALLBACK ISOopen(void) {
                return 0; // it's already open
        }
 
-       cdHandle = fopen_utf8(GetIsoFile(), "rb");
+       cdHandle = fopen(GetIsoFile(), "rb");
        if (cdHandle == NULL) {
                SysPrintf(_("Could't open '%s' for reading: %s\n"),
                        GetIsoFile(), strerror(errno));
@@ -1697,7 +1702,7 @@ static long CALLBACK ISOopen(void) {
                        p = alt_bin_filename + strlen(alt_bin_filename) - 4;
                        for (i = 0; i < sizeof(exts) / sizeof(exts[0]); i++) {
                                strcpy(p, exts[i]);
-                               tmpf = fopen_utf8(alt_bin_filename, "rb");
+                               tmpf = fopen(alt_bin_filename, "rb");
                                if (tmpf != NULL)
                                        break;
                        }
@@ -1733,7 +1738,7 @@ static long CALLBACK ISOopen(void) {
 
        // make sure we have another handle open for cdda
        if (numtracks > 1 && ti[1].handle == NULL) {
-               ti[1].handle = fopen_utf8(bin_filename, "rb");
+               ti[1].handle = fopen(bin_filename, "rb");
        }
        cdda_cur_sector = 0;
        cdda_file_offset = 0;
index 53edfab..70de76b 100644 (file)
@@ -23,8 +23,6 @@
 
 // TODO: Implement caches & cycle penalty.
 
-#include <compat/fopen_utf8.h>
-
 #include "psxmem.h"
 #include "psxmem_map.h"
 #include "r3000a.h"
 
 #include "memmap.h"
 
+#ifdef USE_LIBRETRO_VFS
+#include <streams/file_stream_transforms.h>
+#endif
+
 #ifndef MAP_ANONYMOUS
 #define MAP_ANONYMOUS MAP_ANON
 #endif
@@ -219,7 +221,7 @@ void psxMemReset() {
 
        if (strcmp(Config.Bios, "HLE") != 0) {
                sprintf(bios, "%s/%s", Config.BiosDir, Config.Bios);
-               f = fopen_utf8(bios, "rb");
+               f = fopen(bios, "rb");
 
                if (f == NULL) {
                        SysMessage(_("Could not open BIOS:\"%s\". Enabling HLE Bios!\n"), bios);
index ae3e634..4e26907 100644 (file)
@@ -17,8 +17,6 @@
  *   51 Franklin Street, Fifth Floor, Boston, MA 02111-1307 USA.           *
  ***************************************************************************/
 
-#include <compat/fopen_utf8.h>
-
 /*
 * SIO functions.
 */
 #include "sio.h"
 #include <sys/stat.h>
 
+#ifdef USE_LIBRETRO_VFS
+#include <streams/file_stream_transforms.h>
+#endif
+
 // Status Flags
 #define TX_RDY         0x0001
 #define RX_RDY         0x0002
@@ -438,11 +440,11 @@ void LoadMcd(int mcd, char *str) {
        if (*str == 0)
                return;
 
-       f = fopen_utf8(str, "rb");
+       f = fopen(str, "rb");
        if (f == NULL) {
                SysPrintf(_("The memory card %s doesn't exist - creating it\n"), str);
                CreateMcd(str);
-               f = fopen_utf8(str, "rb");
+               f = fopen(str, "rb");
                if (f != NULL) {
                        struct stat buf;
 
@@ -483,7 +485,7 @@ void SaveMcd(char *mcd, char *data, uint32_t adr, int size) {
        if (mcd == NULL || *mcd == 0 || strcmp(mcd, "none") == 0)
                return;
 
-       f = fopen_utf8(mcd, "r+b");
+       f = fopen(mcd, "r+b");
        if (f != NULL) {
                struct stat buf;
 
@@ -520,7 +522,7 @@ void CreateMcd(char *mcd) {
        int s = MCD_SIZE;
        int i = 0, j;
 
-       f = fopen_utf8(mcd, "wb");
+       f = fopen(mcd, "wb");
        if (f == NULL)
                return;
 
diff --git a/libretro-common/compat/compat_posix_string.c b/libretro-common/compat/compat_posix_string.c
new file mode 100644 (file)
index 0000000..6a2f07e
--- /dev/null
@@ -0,0 +1,104 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (compat_posix_string.c).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <ctype.h>
+
+#include <compat/posix_string.h>
+
+#ifdef _WIN32
+
+#undef strcasecmp
+#undef strdup
+#undef isblank
+#undef strtok_r
+#include <ctype.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <compat/strl.h>
+
+#include <string.h>
+
+int retro_strcasecmp__(const char *a, const char *b)
+{
+   while (*a && *b)
+   {
+      int a_ = tolower(*a);
+      int b_ = tolower(*b);
+
+      if (a_ != b_)
+         return a_ - b_;
+
+      a++;
+      b++;
+   }
+
+   return tolower(*a) - tolower(*b);
+}
+
+char *retro_strdup__(const char *orig)
+{
+   size_t len = strlen(orig) + 1;
+   char *ret  = (char*)malloc(len);
+   if (!ret)
+      return NULL;
+
+   strlcpy(ret, orig, len);
+   return ret;
+}
+
+int retro_isblank__(int c)
+{
+   return (c == ' ') || (c == '\t');
+}
+
+char *retro_strtok_r__(char *str, const char *delim, char **saveptr)
+{
+   char *first = NULL;
+   if (!saveptr || !delim)
+      return NULL;
+
+   if (str)
+      *saveptr = str;
+
+   do
+   {
+      char *ptr = NULL;
+      first = *saveptr;
+      while (*first && strchr(delim, *first))
+         *first++ = '\0';
+
+      if (*first == '\0')
+         return NULL;
+
+      ptr = first + 1;
+
+      while (*ptr && !strchr(delim, *ptr))
+         ptr++;
+
+      *saveptr = ptr + (*ptr ? 1 : 0);
+      *ptr     = '\0';
+   } while (strlen(first) == 0);
+
+   return first;
+}
+
+#endif
diff --git a/libretro-common/compat/compat_strcasestr.c b/libretro-common/compat/compat_strcasestr.c
new file mode 100644 (file)
index 0000000..4129dab
--- /dev/null
@@ -0,0 +1,58 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (compat_strcasestr.c).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <ctype.h>
+
+#include <compat/strcasestr.h>
+
+/* Pretty much strncasecmp. */
+static int casencmp(const char *a, const char *b, size_t n)
+{
+   size_t i;
+
+   for (i = 0; i < n; i++)
+   {
+      int a_lower = tolower(a[i]);
+      int b_lower = tolower(b[i]);
+      if (a_lower != b_lower)
+         return a_lower - b_lower;
+   }
+
+   return 0;
+}
+
+char *strcasestr_retro__(const char *haystack, const char *needle)
+{
+   size_t i, search_off;
+   size_t hay_len    = strlen(haystack);
+   size_t needle_len = strlen(needle);
+
+   if (needle_len > hay_len)
+      return NULL;
+
+   search_off = hay_len - needle_len;
+   for (i = 0; i <= search_off; i++)
+      if (!casencmp(haystack + i, needle, needle_len))
+         return (char*)haystack + i;
+
+   return NULL;
+}
diff --git a/libretro-common/file/file_path.c b/libretro-common/file/file_path.c
new file mode 100644 (file)
index 0000000..3dd53c9
--- /dev/null
@@ -0,0 +1,1293 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (file_path.c).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+
+#include <sys/stat.h>
+
+#include <boolean.h>
+#include <file/file_path.h>
+#include <retro_assert.h>
+#include <string/stdstring.h>
+#include <time/rtime.h>
+
+/* TODO: There are probably some unnecessary things on this huge include list now but I'm too afraid to touch it */
+#ifdef __APPLE__
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+#ifdef __HAIKU__
+#include <kernel/image.h>
+#endif
+#ifndef __MACH__
+#include <compat/strl.h>
+#include <compat/posix_string.h>
+#endif
+#include <retro_miscellaneous.h>
+#include <encodings/utf.h>
+
+#if defined(_WIN32)
+#ifdef _MSC_VER
+#define setmode _setmode
+#endif
+#include <sys/stat.h>
+#ifdef _XBOX
+#include <xtl.h>
+#define INVALID_FILE_ATTRIBUTES -1
+#else
+#include <io.h>
+#include <fcntl.h>
+#include <direct.h>
+#include <windows.h>
+#if defined(_MSC_VER) && _MSC_VER <= 1200
+#define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
+#endif
+#endif
+#elif defined(VITA)
+#define SCE_ERROR_ERRNO_EEXIST 0x80010011
+#include <psp2/io/fcntl.h>
+#include <psp2/io/dirent.h>
+#include <psp2/io/stat.h>
+#else
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+#if defined(PSP)
+#include <pspkernel.h>
+#endif
+
+#if defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
+#include <cell/cell_fs.h>
+#endif
+
+#if defined(VITA)
+#define FIO_S_ISDIR SCE_S_ISDIR
+#endif
+
+#if (defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)) || defined(__QNX__) || defined(PSP)
+#include <unistd.h> /* stat() is defined here */
+#endif
+
+#if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL)
+#ifdef __WINRT__
+#include <uwp/uwp_func.h>
+#endif
+#endif
+
+/* Assume W-functions do not work below Win2K and Xbox platforms */
+#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 || defined(_XBOX)
+
+#ifndef LEGACY_WIN32
+#define LEGACY_WIN32
+#endif
+
+#endif
+
+/**
+ * path_get_archive_delim:
+ * @path               : path
+ *
+ * Find delimiter of an archive file. Only the first '#'
+ * after a compression extension is considered.
+ *
+ * Returns: pointer to the delimiter in the path if it contains
+ * a path inside a compressed file, otherwise NULL.
+ */
+const char *path_get_archive_delim(const char *path)
+{
+   const char *last_slash = find_last_slash(path);
+   const char *delim      = NULL;
+   char buf[5];
+
+   buf[0] = '\0';
+
+   if (!last_slash)
+      return NULL;
+
+   /* Find delimiter position */
+   delim = strrchr(last_slash, '#');
+
+   if (!delim)
+      return NULL;
+
+   /* Check whether this is a known archive type
+    * > Note: The code duplication here is
+    *   deliberate, to maximise performance */
+   if (delim - last_slash > 4)
+   {
+      strlcpy(buf, delim - 4, sizeof(buf));
+      buf[4] = '\0';
+
+      string_to_lower(buf);
+
+      /* Check if this is a '.zip', '.apk' or '.7z' file */
+      if (string_is_equal(buf,     ".zip") ||
+          string_is_equal(buf,     ".apk") ||
+          string_is_equal(buf + 1, ".7z"))
+         return delim;
+   }
+   else if (delim - last_slash > 3)
+   {
+      strlcpy(buf, delim - 3, sizeof(buf));
+      buf[3] = '\0';
+
+      string_to_lower(buf);
+
+      /* Check if this is a '.7z' file */
+      if (string_is_equal(buf, ".7z"))
+         return delim;
+   }
+
+   return NULL;
+}
+
+/**
+ * path_get_extension:
+ * @path               : path
+ *
+ * Gets extension of file. Only '.'s
+ * after the last slash are considered.
+ *
+ * Returns: extension part from the path.
+ */
+const char *path_get_extension(const char *path)
+{
+   const char *ext;
+   if (!string_is_empty(path) && ((ext = strrchr(path_basename(path), '.'))))
+      return ext + 1;
+   return "";
+}
+
+/**
+ * path_remove_extension:
+ * @path               : path
+ *
+ * Mutates path by removing its extension. Removes all
+ * text after and including the last '.'.
+ * Only '.'s after the last slash are considered.
+ *
+ * Returns:
+ * 1) If path has an extension, returns path with the
+ *    extension removed.
+ * 2) If there is no extension, returns NULL.
+ * 3) If path is empty or NULL, returns NULL
+ */
+char *path_remove_extension(char *path)
+{
+   char *last = !string_is_empty(path)
+      ? (char*)strrchr(path_basename(path), '.') : NULL;
+   if (!last)
+      return NULL;
+   if (*last)
+      *last = '\0';
+   return path;
+}
+
+/**
+ * path_is_compressed_file:
+ * @path               : path
+ *
+ * Checks if path is a compressed file.
+ *
+ * Returns: true (1) if path is a compressed file, otherwise false (0).
+ **/
+bool path_is_compressed_file(const char* path)
+{
+   const char *ext = path_get_extension(path);
+
+   if (string_is_empty(ext))
+      return false;
+
+   if (string_is_equal_noncase(ext, "zip") ||
+       string_is_equal_noncase(ext, "apk") ||
+       string_is_equal_noncase(ext, "7z"))
+      return true;
+
+   return false;
+}
+
+/**
+ * fill_pathname:
+ * @out_path           : output path
+ * @in_path            : input  path
+ * @replace            : what to replace
+ * @size               : buffer size of output path
+ *
+ * FIXME: Verify
+ *
+ * Replaces filename extension with 'replace' and outputs result to out_path.
+ * The extension here is considered to be the string from the last '.'
+ * to the end.
+ *
+ * Only '.'s after the last slash are considered as extensions.
+ * If no '.' is present, in_path and replace will simply be concatenated.
+ * 'size' is buffer size of 'out_path'.
+ * E.g.: in_path = "/foo/bar/baz/boo.c", replace = ".asm" =>
+ * out_path = "/foo/bar/baz/boo.asm"
+ * E.g.: in_path = "/foo/bar/baz/boo.c", replace = ""     =>
+ * out_path = "/foo/bar/baz/boo"
+ */
+void fill_pathname(char *out_path, const char *in_path,
+      const char *replace, size_t size)
+{
+   char tmp_path[PATH_MAX_LENGTH];
+   char *tok                      = NULL;
+
+   tmp_path[0] = '\0';
+
+   strlcpy(tmp_path, in_path, sizeof(tmp_path));
+   if ((tok = (char*)strrchr(path_basename(tmp_path), '.')))
+      *tok = '\0';
+
+   fill_pathname_noext(out_path, tmp_path, replace, size);
+}
+
+/**
+ * fill_pathname_noext:
+ * @out_path           : output path
+ * @in_path            : input  path
+ * @replace            : what to replace
+ * @size               : buffer size of output path
+ *
+ * Appends a filename extension 'replace' to 'in_path', and outputs
+ * result in 'out_path'.
+ *
+ * Assumes in_path has no extension. If an extension is still
+ * present in 'in_path', it will be ignored.
+ *
+ */
+size_t fill_pathname_noext(char *out_path, const char *in_path,
+      const char *replace, size_t size)
+{
+   strlcpy(out_path, in_path, size);
+   return strlcat(out_path, replace, size);
+}
+
+char *find_last_slash(const char *str)
+{
+   const char *slash     = strrchr(str, '/');
+#ifdef _WIN32
+   const char *backslash = strrchr(str, '\\');
+
+   if (!slash || (backslash > slash))
+      return (char*)backslash;
+#endif
+   return (char*)slash;
+}
+
+/**
+ * fill_pathname_slash:
+ * @path               : path
+ * @size               : size of path
+ *
+ * Assumes path is a directory. Appends a slash
+ * if not already there.
+ **/
+void fill_pathname_slash(char *path, size_t size)
+{
+   size_t path_len;
+   const char *last_slash = find_last_slash(path);
+
+   if (!last_slash)
+   {
+      strlcat(path, PATH_DEFAULT_SLASH(), size);
+      return;
+   }
+
+   path_len               = strlen(path);
+   /* Try to preserve slash type. */
+   if (last_slash != (path + path_len - 1))
+   {
+      path[path_len]   = last_slash[0];
+      path[path_len+1] = '\0';
+   }
+}
+
+/**
+ * fill_pathname_dir:
+ * @in_dir             : input directory path
+ * @in_basename        : input basename to be appended to @in_dir
+ * @replace            : replacement to be appended to @in_basename
+ * @size               : size of buffer
+ *
+ * Appends basename of 'in_basename', to 'in_dir', along with 'replace'.
+ * Basename of in_basename is the string after the last '/' or '\\',
+ * i.e the filename without directories.
+ *
+ * If in_basename has no '/' or '\\', the whole 'in_basename' will be used.
+ * 'size' is buffer size of 'in_dir'.
+ *
+ * E.g..: in_dir = "/tmp/some_dir", in_basename = "/some_content/foo.c",
+ * replace = ".asm" => in_dir = "/tmp/some_dir/foo.c.asm"
+ **/
+size_t fill_pathname_dir(char *in_dir, const char *in_basename,
+      const char *replace, size_t size)
+{
+   const char *base = NULL;
+
+   fill_pathname_slash(in_dir, size);
+   base = path_basename(in_basename);
+   strlcat(in_dir, base, size);
+   return strlcat(in_dir, replace, size);
+}
+
+/**
+ * fill_pathname_base:
+ * @out                : output path
+ * @in_path            : input path
+ * @size               : size of output path
+ *
+ * Copies basename of @in_path into @out_path.
+ **/
+size_t fill_pathname_base(char *out, const char *in_path, size_t size)
+{
+   const char     *ptr = path_basename(in_path);
+
+   if (!ptr)
+      ptr = in_path;
+
+   return strlcpy(out, ptr, size);
+}
+
+void fill_pathname_base_noext(char *out,
+      const char *in_path, size_t size)
+{
+   fill_pathname_base(out, in_path, size);
+   path_remove_extension(out);
+}
+
+size_t fill_pathname_base_ext(char *out,
+      const char *in_path, const char *ext,
+      size_t size)
+{
+   fill_pathname_base_noext(out, in_path, size);
+   return strlcat(out, ext, size);
+}
+
+/**
+ * fill_pathname_basedir:
+ * @out_dir            : output directory
+ * @in_path            : input path
+ * @size               : size of output directory
+ *
+ * Copies base directory of @in_path into @out_path.
+ * If in_path is a path without any slashes (relative current directory),
+ * @out_path will get path "./".
+ **/
+void fill_pathname_basedir(char *out_dir,
+      const char *in_path, size_t size)
+{
+   if (out_dir != in_path)
+      strlcpy(out_dir, in_path, size);
+   path_basedir(out_dir);
+}
+
+void fill_pathname_basedir_noext(char *out_dir,
+      const char *in_path, size_t size)
+{
+   fill_pathname_basedir(out_dir, in_path, size);
+   path_remove_extension(out_dir);
+}
+
+/**
+ * fill_pathname_parent_dir_name:
+ * @out_dir            : output directory
+ * @in_dir             : input directory
+ * @size               : size of output directory
+ *
+ * Copies only the parent directory name of @in_dir into @out_dir.
+ * The two buffers must not overlap. Removes trailing '/'.
+ * Returns true on success, false if a slash was not found in the path.
+ **/
+bool fill_pathname_parent_dir_name(char *out_dir,
+      const char *in_dir, size_t size)
+{
+   bool success = false;
+   char *temp   = strdup(in_dir);
+   char *last   = find_last_slash(temp);
+
+   if (last && last[1] == 0)
+   {
+      *last     = '\0';
+      last      = find_last_slash(temp);
+   }
+
+   if (last)
+      *last     = '\0';
+
+   in_dir       = find_last_slash(temp);
+
+   success      = in_dir && in_dir[1];
+
+   if (success)
+      strlcpy(out_dir, in_dir + 1, size);
+
+   free(temp);
+   return success;
+}
+
+/**
+ * fill_pathname_parent_dir:
+ * @out_dir            : output directory
+ * @in_dir             : input directory
+ * @size               : size of output directory
+ *
+ * Copies parent directory of @in_dir into @out_dir.
+ * Assumes @in_dir is a directory. Keeps trailing '/'.
+ * If the path was already at the root directory, @out_dir will be an empty string.
+ **/
+void fill_pathname_parent_dir(char *out_dir,
+      const char *in_dir, size_t size)
+{
+   if (out_dir != in_dir)
+      strlcpy(out_dir, in_dir, size);
+   path_parent_dir(out_dir);
+}
+
+/**
+ * fill_dated_filename:
+ * @out_filename       : output filename
+ * @ext                : extension of output filename
+ * @size               : buffer size of output filename
+ *
+ * Creates a 'dated' filename prefixed by 'RetroArch', and
+ * concatenates extension (@ext) to it.
+ *
+ * E.g.:
+ * out_filename = "RetroArch-{month}{day}-{Hours}{Minutes}.{@ext}"
+ **/
+size_t fill_dated_filename(char *out_filename,
+      const char *ext, size_t size)
+{
+   time_t cur_time = time(NULL);
+   struct tm tm_;
+
+   rtime_localtime(&cur_time, &tm_);
+
+   strftime(out_filename, size,
+         "RetroArch-%m%d-%H%M%S", &tm_);
+   return strlcat(out_filename, ext, size);
+}
+
+/**
+ * fill_str_dated_filename:
+ * @out_filename       : output filename
+ * @in_str             : input string
+ * @ext                : extension of output filename
+ * @size               : buffer size of output filename
+ *
+ * Creates a 'dated' filename prefixed by the string @in_str, and
+ * concatenates extension (@ext) to it.
+ *
+ * E.g.:
+ * out_filename = "RetroArch-{year}{month}{day}-{Hour}{Minute}{Second}.{@ext}"
+ **/
+void fill_str_dated_filename(char *out_filename,
+      const char *in_str, const char *ext, size_t size)
+{
+   char format[256];
+   struct tm tm_;
+   time_t cur_time = time(NULL);
+
+   format[0]       = '\0';
+
+   rtime_localtime(&cur_time, &tm_);
+
+   if (string_is_empty(ext))
+   {
+      strftime(format, sizeof(format), "-%y%m%d-%H%M%S", &tm_);
+      fill_pathname_noext(out_filename, in_str, format, size);
+   }
+   else
+   {
+      strftime(format, sizeof(format), "-%y%m%d-%H%M%S.", &tm_);
+
+      fill_pathname_join_concat_noext(out_filename,
+            in_str, format, ext,
+            size);
+   }
+}
+
+/**
+ * path_basedir:
+ * @path               : path
+ *
+ * Extracts base directory by mutating path.
+ * Keeps trailing '/'.
+ **/
+void path_basedir(char *path)
+{
+   char *last = NULL;
+
+   if (strlen(path) < 2)
+      return;
+
+   last = find_last_slash(path);
+
+   if (last)
+      last[1] = '\0';
+   else
+      snprintf(path, 3, "." PATH_DEFAULT_SLASH());
+}
+
+/**
+ * path_parent_dir:
+ * @path               : path
+ *
+ * Extracts parent directory by mutating path.
+ * Assumes that path is a directory. Keeps trailing '/'.
+ * If the path was already at the root directory, returns empty string
+ **/
+void path_parent_dir(char *path)
+{
+   size_t len = 0;
+
+   if (!path)
+      return;
+   
+   len = strlen(path);
+
+   if (len && PATH_CHAR_IS_SLASH(path[len - 1]))
+   {
+      bool path_was_absolute = path_is_absolute(path);
+
+      path[len - 1] = '\0';
+
+      if (path_was_absolute && !find_last_slash(path))
+      {
+         /* We removed the only slash from what used to be an absolute path.
+          * On Linux, this goes from "/" to an empty string and everything works fine,
+          * but on Windows, we went from C:\ to C:, which is not a valid path and that later
+          * gets errornously treated as a relative one by path_basedir and returns "./".
+          * What we really wanted is an empty string. */
+         path[0] = '\0';
+         return;
+      }
+   }
+   path_basedir(path);
+}
+
+/**
+ * path_basename:
+ * @path               : path
+ *
+ * Get basename from @path.
+ *
+ * Returns: basename from path.
+ **/
+const char *path_basename(const char *path)
+{
+   /* We cut at the first compression-related hash */
+   const char *delim = path_get_archive_delim(path);
+   if (delim)
+      return delim + 1;
+
+   {
+      /* We cut at the last slash */
+      const char *last  = find_last_slash(path);
+      if (last)
+         return last + 1;
+   }
+
+   return path;
+}
+
+/**
+ * path_is_absolute:
+ * @path               : path
+ *
+ * Checks if @path is an absolute path or a relative path.
+ *
+ * Returns: true if path is absolute, false if path is relative.
+ **/
+bool path_is_absolute(const char *path)
+{
+   if (string_is_empty(path))
+      return false;
+
+   if (path[0] == '/')
+      return true;
+
+#if defined(_WIN32)
+   /* Many roads lead to Rome...
+    * Note: Drive letter can only be 1 character long */
+   if (string_starts_with_size(path,     "\\\\", STRLEN_CONST("\\\\")) ||
+       string_starts_with_size(path + 1, ":/",   STRLEN_CONST(":/"))   ||
+       string_starts_with_size(path + 1, ":\\",  STRLEN_CONST(":\\")))
+      return true;
+#elif defined(__wiiu__) || defined(VITA)
+   {
+      const char *seperator = strchr(path, ':');
+      if (seperator && (seperator[1] == '/'))
+         return true;
+   }
+#endif
+
+   return false;
+}
+
+/**
+ * path_resolve_realpath:
+ * @buf                : input and output buffer for path
+ * @size               : size of buffer
+ * @resolve_symlinks   : whether to resolve symlinks or not
+ *
+ * Resolves use of ".", "..", multiple slashes etc in absolute paths.
+ *
+ * Relative paths are rebased on the current working dir.
+ *
+ * Returns: @buf if successful, NULL otherwise.
+ * Note: Not implemented on consoles
+ * Note: Symlinks are only resolved on Unix-likes
+ * Note: The current working dir might not be what you expect,
+ *       e.g. on Android it is "/"
+ *       Use of fill_pathname_resolve_relative() should be prefered
+ **/
+char *path_resolve_realpath(char *buf, size_t size, bool resolve_symlinks)
+{
+#if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL)
+   char tmp[PATH_MAX_LENGTH];
+#ifdef _WIN32
+   strlcpy(tmp, buf, sizeof(tmp));
+   if (!_fullpath(buf, tmp, size))
+   {
+      strlcpy(buf, tmp, size);
+      return NULL;
+   }
+   return buf;
+#else
+   size_t t;
+   char *p;
+   const char *next;
+   const char *buf_end;
+
+   if (resolve_symlinks)
+   {
+      strlcpy(tmp, buf, sizeof(tmp));
+
+      /* NOTE: realpath() expects at least PATH_MAX_LENGTH bytes in buf.
+       * Technically, PATH_MAX_LENGTH needn't be defined, but we rely on it anyways.
+       * POSIX 2008 can automatically allocate for you,
+       * but don't rely on that. */
+      if (!realpath(tmp, buf))
+      {
+         strlcpy(buf, tmp, size);
+         return NULL;
+      }
+
+      return buf;
+   }
+
+   t = 0; /* length of output */
+   buf_end = buf + strlen(buf);
+
+   if (!path_is_absolute(buf))
+   {
+      size_t len;
+      /* rebase on working directory */
+      if (!getcwd(tmp, PATH_MAX_LENGTH-1))
+         return NULL;
+
+      len = strlen(tmp);
+      t += len;
+
+      if (tmp[len-1] != '/')
+         tmp[t++] = '/';
+
+      if (string_is_empty(buf))
+         goto end;
+
+      p = buf;
+   }
+   else
+   {
+      /* UNIX paths can start with multiple '/', copy those */
+      for (p = buf; *p == '/'; p++)
+         tmp[t++] = '/';
+   }
+
+   /* p points to just after a slash while 'next' points to the next slash
+    * if there are no slashes, they point relative to where one would be */
+   do
+   {
+      next = strchr(p, '/');
+      if (!next)
+         next = buf_end;
+
+      if ((next - p == 2 && p[0] == '.' && p[1] == '.'))
+      {
+         p += 3;
+
+         /* fail for illegal /.., //.. etc */
+         if (t == 1 || tmp[t-2] == '/')
+            return NULL;
+
+         /* delete previous segment in tmp by adjusting size t
+          * tmp[t-1] == '/', find '/' before that */
+         t = t-2;
+         while (tmp[t] != '/')
+            t--;
+         t++;
+      }
+      else if (next - p == 1 && p[0] == '.')
+         p += 2;
+      else if (next - p == 0)
+         p += 1;
+      else
+      {
+         /* fail when truncating */
+         if (t + next-p+1 > PATH_MAX_LENGTH-1)
+            return NULL;
+
+         while (p <= next)
+            tmp[t++] = *p++;
+      }
+
+   }
+   while (next < buf_end);
+
+end:
+   tmp[t] = '\0';
+   strlcpy(buf, tmp, size);
+   return buf;
+#endif
+#endif
+   return NULL;
+}
+
+/**
+ * path_relative_to:
+ * @out                : buffer to write the relative path to
+ * @path               : path to be expressed relatively
+ * @base               : base directory to start out on
+ * @size               : size of output buffer
+ *
+ * Turns @path into a path relative to @base and writes it to @out.
+ *
+ * @base is assumed to be a base directory, i.e. a path ending with '/' or '\'.
+ * Both @path and @base are assumed to be absolute paths without "." or "..".
+ *
+ * E.g. path /a/b/e/f.cg with base /a/b/c/d/ turns into ../../e/f.cg
+ **/
+size_t path_relative_to(char *out,
+      const char *path, const char *base, size_t size)
+{
+   size_t i, j;
+   const char *trimmed_path, *trimmed_base;
+
+#ifdef _WIN32
+   /* For different drives, return absolute path */
+   if (strlen(path) >= 2 && strlen(base) >= 2
+         && path[1] == ':' && base[1] == ':'
+         && path[0] != base[0])
+      return strlcpy(out, path, size);
+#endif
+
+   /* Trim common beginning */
+   for (i = 0, j = 0; path[i] && base[i] && path[i] == base[i]; i++)
+      if (path[i] == PATH_DEFAULT_SLASH_C())
+         j = i + 1;
+
+   trimmed_path = path+j;
+   trimmed_base = base+i;
+
+   /* Each segment of base turns into ".." */
+   out[0] = '\0';
+   for (i = 0; trimmed_base[i]; i++)
+      if (trimmed_base[i] == PATH_DEFAULT_SLASH_C())
+         strlcat(out, ".." PATH_DEFAULT_SLASH(), size);
+
+   return strlcat(out, trimmed_path, size);
+}
+
+/**
+ * fill_pathname_resolve_relative:
+ * @out_path           : output path
+ * @in_refpath         : input reference path
+ * @in_path            : input path
+ * @size               : size of @out_path
+ *
+ * Joins basedir of @in_refpath together with @in_path.
+ * If @in_path is an absolute path, out_path = in_path.
+ * E.g.: in_refpath = "/foo/bar/baz.a", in_path = "foobar.cg",
+ * out_path = "/foo/bar/foobar.cg".
+ **/
+void fill_pathname_resolve_relative(char *out_path,
+      const char *in_refpath, const char *in_path, size_t size)
+{
+   if (path_is_absolute(in_path))
+   {
+      strlcpy(out_path, in_path, size);
+      return;
+   }
+
+   fill_pathname_basedir(out_path, in_refpath, size);
+   strlcat(out_path, in_path, size);
+   path_resolve_realpath(out_path, size, false);
+}
+
+/**
+ * fill_pathname_join:
+ * @out_path           : output path
+ * @dir                : directory
+ * @path               : path
+ * @size               : size of output path
+ *
+ * Joins a directory (@dir) and path (@path) together.
+ * Makes sure not to get  two consecutive slashes
+ * between directory and path.
+ **/
+size_t fill_pathname_join(char *out_path,
+      const char *dir, const char *path, size_t size)
+{
+   if (out_path != dir)
+      strlcpy(out_path, dir, size);
+
+   if (*out_path)
+      fill_pathname_slash(out_path, size);
+
+   return strlcat(out_path, path, size);
+}
+
+size_t fill_pathname_join_special_ext(char *out_path,
+      const char *dir,  const char *path,
+      const char *last, const char *ext,
+      size_t size)
+{
+   fill_pathname_join(out_path, dir, path, size);
+   if (*out_path)
+      fill_pathname_slash(out_path, size);
+
+   strlcat(out_path, last, size);
+   return strlcat(out_path, ext, size);
+}
+
+size_t fill_pathname_join_concat_noext(char *out_path,
+      const char *dir, const char *path,
+      const char *concat,
+      size_t size)
+{
+   fill_pathname_noext(out_path, dir, path, size);
+   return strlcat(out_path, concat, size);
+}
+
+size_t fill_pathname_join_concat(char *out_path,
+      const char *dir, const char *path,
+      const char *concat,
+      size_t size)
+{
+   fill_pathname_join(out_path, dir, path, size);
+   return strlcat(out_path, concat, size);
+}
+
+void fill_pathname_join_noext(char *out_path,
+      const char *dir, const char *path, size_t size)
+{
+   fill_pathname_join(out_path, dir, path, size);
+   path_remove_extension(out_path);
+}
+
+/**
+ * fill_pathname_join_delim:
+ * @out_path           : output path
+ * @dir                : directory
+ * @path               : path
+ * @delim              : delimiter
+ * @size               : size of output path
+ *
+ * Joins a directory (@dir) and path (@path) together
+ * using the given delimiter (@delim).
+ **/
+size_t fill_pathname_join_delim(char *out_path, const char *dir,
+      const char *path, const char delim, size_t size)
+{
+   size_t copied;
+   /* behavior of strlcpy is undefined if dst and src overlap */
+   if (out_path == dir)
+      copied = strlen(dir);
+   else
+      copied = strlcpy(out_path, dir, size);
+
+   out_path[copied]   = delim;
+   out_path[copied+1] = '\0';
+
+   if (path)
+      copied = strlcat(out_path, path, size);
+   return copied;
+}
+
+size_t fill_pathname_join_delim_concat(char *out_path, const char *dir,
+      const char *path, const char delim, const char *concat,
+      size_t size)
+{
+   fill_pathname_join_delim(out_path, dir, path, delim, size);
+   return strlcat(out_path, concat, size);
+}
+
+/**
+ * fill_short_pathname_representation:
+ * @out_rep            : output representation
+ * @in_path            : input path
+ * @size               : size of output representation
+ *
+ * Generates a short representation of path. It should only
+ * be used for displaying the result; the output representation is not
+ * binding in any meaningful way (for a normal path, this is the same as basename)
+ * In case of more complex URLs, this should cut everything except for
+ * the main image file.
+ *
+ * E.g.: "/path/to/game.img" -> game.img
+ *       "/path/to/myarchive.7z#folder/to/game.img" -> game.img
+ */
+size_t fill_short_pathname_representation(char* out_rep,
+      const char *in_path, size_t size)
+{
+   char path_short[PATH_MAX_LENGTH];
+
+   path_short[0] = '\0';
+
+   fill_pathname(path_short, path_basename(in_path), "",
+            sizeof(path_short));
+
+   return strlcpy(out_rep, path_short, size);
+}
+
+void fill_short_pathname_representation_noext(char* out_rep,
+      const char *in_path, size_t size)
+{
+   fill_short_pathname_representation(out_rep, in_path, size);
+   path_remove_extension(out_rep);
+}
+
+void fill_pathname_expand_special(char *out_path,
+      const char *in_path, size_t size)
+{
+#if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL)
+   if (in_path[0] == '~')
+   {
+      char *home_dir = (char*)malloc(PATH_MAX_LENGTH * sizeof(char));
+
+      home_dir[0] = '\0';
+
+      fill_pathname_home_dir(home_dir,
+         PATH_MAX_LENGTH * sizeof(char));
+
+      if (*home_dir)
+      {
+         size_t src_size = strlcpy(out_path, home_dir, size);
+         retro_assert(src_size < size);
+
+         out_path  += src_size;
+         size      -= src_size;
+
+         if (!PATH_CHAR_IS_SLASH(out_path[-1]))
+         {
+            src_size = strlcpy(out_path, PATH_DEFAULT_SLASH(), size);
+            retro_assert(src_size < size);
+
+            out_path += src_size;
+            size     -= src_size;
+         }
+
+         in_path += 2;
+      }
+
+      free(home_dir);
+   }
+   else if (in_path[0] == ':')
+   {
+      char *application_dir = (char*)malloc(PATH_MAX_LENGTH * sizeof(char));
+
+      application_dir[0]    = '\0';
+
+      fill_pathname_application_dir(application_dir,
+            PATH_MAX_LENGTH * sizeof(char));
+
+      if (*application_dir)
+      {
+         size_t src_size   = strlcpy(out_path, application_dir, size);
+         retro_assert(src_size < size);
+
+         out_path  += src_size;
+         size      -= src_size;
+
+         if (!PATH_CHAR_IS_SLASH(out_path[-1]))
+         {
+            src_size = strlcpy(out_path, PATH_DEFAULT_SLASH(), size);
+            retro_assert(src_size < size);
+
+            out_path += src_size;
+            size     -= src_size;
+         }
+
+         in_path += 2;
+      }
+
+      free(application_dir);
+   }
+#endif
+
+   retro_assert(strlcpy(out_path, in_path, size) < size);
+}
+
+void fill_pathname_abbreviate_special(char *out_path,
+      const char *in_path, size_t size)
+{
+#if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL)
+   unsigned i;
+   const char *candidates[3];
+   const char *notations[3];
+   char application_dir[PATH_MAX_LENGTH];
+   char home_dir[PATH_MAX_LENGTH];
+
+   application_dir[0] = '\0';
+   home_dir[0]        = '\0';
+
+   /* application_dir could be zero-string. Safeguard against this.
+    *
+    * Keep application dir in front of home, moving app dir to a
+    * new location inside home would break otherwise. */
+
+   /* ugly hack - use application_dir pointer
+    * before filling it in. C89 reasons */
+   candidates[0] = application_dir;
+   candidates[1] = home_dir;
+   candidates[2] = NULL;
+
+   notations [0] = ":";
+   notations [1] = "~";
+   notations [2] = NULL;
+
+   fill_pathname_application_dir(application_dir, sizeof(application_dir));
+   fill_pathname_home_dir(home_dir, sizeof(home_dir));
+
+   for (i = 0; candidates[i]; i++)
+   {
+      if (!string_is_empty(candidates[i]) &&
+          string_starts_with(in_path, candidates[i]))
+      {
+         size_t src_size  = strlcpy(out_path, notations[i], size);
+
+         retro_assert(src_size < size);
+
+         out_path        += src_size;
+         size            -= src_size;
+         in_path         += strlen(candidates[i]);
+
+         if (!PATH_CHAR_IS_SLASH(*in_path))
+         {
+            strcpy_literal(out_path, PATH_DEFAULT_SLASH());
+            out_path++;
+            size--;
+         }
+
+         break; /* Don't allow more abbrevs to take place. */
+      }
+   }
+
+#endif
+
+   retro_assert(strlcpy(out_path, in_path, size) < size);
+}
+
+/**
+ * path_basedir:
+ * @path               : path
+ *
+ * Extracts base directory by mutating path.
+ * Keeps trailing '/'.
+ **/
+void path_basedir_wrapper(char *path)
+{
+   char *last = NULL;
+   if (strlen(path) < 2)
+      return;
+
+#ifdef HAVE_COMPRESSION
+   /* We want to find the directory with the archive in basedir. */
+   last = (char*)path_get_archive_delim(path);
+   if (last)
+      *last = '\0';
+#endif
+
+   last = find_last_slash(path);
+
+   if (last)
+      last[1] = '\0';
+   else
+      snprintf(path, 3, "." PATH_DEFAULT_SLASH());
+}
+
+#if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL)
+void fill_pathname_application_path(char *s, size_t len)
+{
+   size_t i;
+#ifdef __APPLE__
+  CFBundleRef bundle = CFBundleGetMainBundle();
+#endif
+#ifdef _WIN32
+   DWORD ret = 0;
+   wchar_t wstr[PATH_MAX_LENGTH] = {0};
+#endif
+#ifdef __HAIKU__
+   image_info info;
+   int32_t cookie = 0;
+#endif
+   (void)i;
+
+   if (!len)
+      return;
+
+#if defined(_WIN32)
+#ifdef LEGACY_WIN32
+   ret    = GetModuleFileNameA(NULL, s, len);
+#else
+   ret    = GetModuleFileNameW(NULL, wstr, ARRAY_SIZE(wstr));
+
+   if (*wstr)
+   {
+      char *str = utf16_to_utf8_string_alloc(wstr);
+
+      if (str)
+      {
+         strlcpy(s, str, len);
+         free(str);
+      }
+   }
+#endif
+   s[ret] = '\0';
+#elif defined(__APPLE__)
+   if (bundle)
+   {
+      CFURLRef bundle_url     = CFBundleCopyBundleURL(bundle);
+      CFStringRef bundle_path = CFURLCopyPath(bundle_url);
+      CFStringGetCString(bundle_path, s, len, kCFStringEncodingUTF8);
+#ifdef HAVE_COCOATOUCH
+      {
+         /* This needs to be done so that the path becomes 
+          * /private/var/... and this
+          * is used consistently throughout for the iOS bundle path */
+         char resolved_bundle_dir_buf[PATH_MAX_LENGTH] = {0};
+         if (realpath(s, resolved_bundle_dir_buf))
+         {
+            strlcpy(s, resolved_bundle_dir_buf, len - 1);
+            strlcat(s, "/", len);
+         }
+      }
+#endif
+
+      CFRelease(bundle_path);
+      CFRelease(bundle_url);
+#ifndef HAVE_COCOATOUCH
+      /* Not sure what this does but it breaks 
+       * stuff for iOS, so skipping */
+      retro_assert(strlcat(s, "nobin", len) < len);
+#endif
+      return;
+   }
+#elif defined(__HAIKU__)
+   while (get_next_image_info(0, &cookie, &info) == B_OK)
+   {
+      if (info.type == B_APP_IMAGE)
+      {
+         strlcpy(s, info.name, len);
+         return;
+      }
+   }
+#elif defined(__QNX__)
+   char *buff = malloc(len);
+
+   if (_cmdname(buff))
+      strlcpy(s, buff, len);
+
+   free(buff);
+#else
+   {
+      pid_t pid;
+      static const char *exts[] = { "exe", "file", "path/a.out" };
+      char link_path[255];
+
+      link_path[0] = *s = '\0';
+      pid       = getpid();
+
+      /* Linux, BSD and Solaris paths. Not standardized. */
+      for (i = 0; i < ARRAY_SIZE(exts); i++)
+      {
+         ssize_t ret;
+
+         snprintf(link_path, sizeof(link_path), "/proc/%u/%s",
+               (unsigned)pid, exts[i]);
+         ret = readlink(link_path, s, len - 1);
+
+         if (ret >= 0)
+         {
+            s[ret] = '\0';
+            return;
+         }
+      }
+   }
+#endif
+}
+
+void fill_pathname_application_dir(char *s, size_t len)
+{
+#ifdef __WINRT__
+   strlcpy(s, uwp_dir_install, len);
+#else
+   fill_pathname_application_path(s, len);
+   path_basedir_wrapper(s);
+#endif
+}
+
+void fill_pathname_home_dir(char *s, size_t len)
+{
+#ifdef __WINRT__
+   const char *home = uwp_dir_data;
+#else
+   const char *home = getenv("HOME");
+#endif
+   if (home)
+      strlcpy(s, home, len);
+   else
+      *s = 0;
+}
+#endif
+
+bool is_path_accessible_using_standard_io(const char *path)
+{
+#ifdef __WINRT__
+   char relative_path_abbrev[PATH_MAX_LENGTH];
+   fill_pathname_abbreviate_special(relative_path_abbrev,
+         path, sizeof(relative_path_abbrev));
+   return (strlen(relative_path_abbrev) >= 2 )
+      &&  (    relative_path_abbrev[0] == ':'
+            || relative_path_abbrev[0] == '~')
+      && PATH_CHAR_IS_SLASH(relative_path_abbrev[1]);
+#else
+   return true;
+#endif
+}
diff --git a/libretro-common/include/compat/posix_string.h b/libretro-common/include/compat/posix_string.h
new file mode 100644 (file)
index 0000000..47964b2
--- /dev/null
@@ -0,0 +1,60 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (posix_string.h).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __LIBRETRO_SDK_COMPAT_POSIX_STRING_H
+#define __LIBRETRO_SDK_COMPAT_POSIX_STRING_H
+
+#include <retro_common_api.h>
+
+#ifdef _MSC_VER
+#include <compat/msvc.h>
+#endif
+
+RETRO_BEGIN_DECLS
+
+#ifdef _WIN32
+#undef strtok_r
+#define strtok_r(str, delim, saveptr) retro_strtok_r__(str, delim, saveptr)
+
+char *strtok_r(char *str, const char *delim, char **saveptr);
+#endif
+
+#ifdef _MSC_VER
+#undef strcasecmp
+#undef strdup
+#define strcasecmp(a, b) retro_strcasecmp__(a, b)
+#define strdup(orig)     retro_strdup__(orig)
+int strcasecmp(const char *a, const char *b);
+char *strdup(const char *orig);
+
+/* isblank is available since MSVC 2013 */
+#if _MSC_VER < 1800
+#undef isblank
+#define isblank(c)       retro_isblank__(c)
+int isblank(int c);
+#endif
+
+#endif
+
+RETRO_END_DECLS
+
+#endif
diff --git a/libretro-common/include/compat/strcasestr.h b/libretro-common/include/compat/strcasestr.h
new file mode 100644 (file)
index 0000000..227e253
--- /dev/null
@@ -0,0 +1,48 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (strcasestr.h).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __LIBRETRO_SDK_COMPAT_STRCASESTR_H
+#define __LIBRETRO_SDK_COMPAT_STRCASESTR_H
+
+#include <string.h>
+
+#if defined(RARCH_INTERNAL) && defined(HAVE_CONFIG_H)
+#include "../../../config.h"
+#endif
+
+#ifndef HAVE_STRCASESTR
+
+#include <retro_common_api.h>
+
+RETRO_BEGIN_DECLS
+
+/* Avoid possible naming collisions during link
+ * since we prefer to use the actual name. */
+#define strcasestr(haystack, needle) strcasestr_retro__(haystack, needle)
+
+char *strcasestr(const char *haystack, const char *needle);
+
+RETRO_END_DECLS
+
+#endif
+
+#endif
diff --git a/libretro-common/include/file/file_path.h b/libretro-common/include/file/file_path.h
new file mode 100644 (file)
index 0000000..6f1918d
--- /dev/null
@@ -0,0 +1,531 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (file_path.h).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __LIBRETRO_SDK_FILE_PATH_H
+#define __LIBRETRO_SDK_FILE_PATH_H
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <sys/types.h>
+
+#include <libretro.h>
+#include <retro_common_api.h>
+
+#include <boolean.h>
+
+RETRO_BEGIN_DECLS
+
+#define PATH_REQUIRED_VFS_VERSION 3
+
+void path_vfs_init(const struct retro_vfs_interface_info* vfs_info);
+
+/* Order in this enum is equivalent to negative sort order in filelist
+ *  (i.e. DIRECTORY is on top of PLAIN_FILE) */
+enum
+{
+   RARCH_FILETYPE_UNSET,
+   RARCH_PLAIN_FILE,
+   RARCH_COMPRESSED_FILE_IN_ARCHIVE,
+   RARCH_COMPRESSED_ARCHIVE,
+   RARCH_DIRECTORY,
+   RARCH_FILE_UNSUPPORTED
+};
+
+/**
+ * path_is_compressed_file:
+ * @path               : path
+ *
+ * Checks if path is a compressed file.
+ *
+ * Returns: true (1) if path is a compressed file, otherwise false (0).
+ **/
+bool path_is_compressed_file(const char *path);
+
+/**
+ * path_contains_compressed_file:
+ * @path               : path
+ *
+ * Checks if path contains a compressed file.
+ *
+ * Currently we only check for hash symbol (#) inside the pathname.
+ * If path is ever expanded to a general URI, we should check for that here.
+ *
+ * Example:  Somewhere in the path there might be a compressed file
+ * E.g.: /path/to/file.7z#mygame.img
+ *
+ * Returns: true (1) if path contains compressed file, otherwise false (0).
+ **/
+#define path_contains_compressed_file(path) (path_get_archive_delim((path)) != NULL)
+
+/**
+ * path_get_archive_delim:
+ * @path               : path
+ *
+ * Gets delimiter of an archive file. Only the first '#'
+ * after a compression extension is considered.
+ *
+ * Returns: pointer to the delimiter in the path if it contains
+ * a compressed file, otherwise NULL.
+ */
+const char *path_get_archive_delim(const char *path);
+
+/**
+ * path_get_extension:
+ * @path               : path
+ *
+ * Gets extension of file. Only '.'s
+ * after the last slash are considered.
+ *
+ * Returns: extension part from the path.
+ */
+const char *path_get_extension(const char *path);
+
+/**
+ * path_remove_extension:
+ * @path               : path
+ *
+ * Mutates path by removing its extension. Removes all
+ * text after and including the last '.'.
+ * Only '.'s after the last slash are considered.
+ *
+ * Returns:
+ * 1) If path has an extension, returns path with the
+ *    extension removed.
+ * 2) If there is no extension, returns NULL.
+ * 3) If path is empty or NULL, returns NULL
+ */
+char *path_remove_extension(char *path);
+
+/**
+ * path_basename:
+ * @path               : path
+ *
+ * Get basename from @path.
+ *
+ * Returns: basename from path.
+ **/
+const char *path_basename(const char *path);
+
+/**
+ * path_basedir:
+ * @path               : path
+ *
+ * Extracts base directory by mutating path.
+ * Keeps trailing '/'.
+ **/
+void path_basedir(char *path);
+
+/**
+ * path_parent_dir:
+ * @path               : path
+ *
+ * Extracts parent directory by mutating path.
+ * Assumes that path is a directory. Keeps trailing '/'.
+ * If the path was already at the root directory, returns empty string
+ **/
+void path_parent_dir(char *path);
+
+/**
+ * path_resolve_realpath:
+ * @buf                : input and output buffer for path
+ * @size               : size of buffer
+ * @resolve_symlinks   : whether to resolve symlinks or not
+ *
+ * Resolves use of ".", "..", multiple slashes etc in absolute paths.
+ *
+ * Relative paths are rebased on the current working dir.
+ *
+ * Returns: @buf if successful, NULL otherwise.
+ * Note: Not implemented on consoles
+ * Note: Symlinks are only resolved on Unix-likes
+ * Note: The current working dir might not be what you expect,
+ *       e.g. on Android it is "/"
+ *       Use of fill_pathname_resolve_relative() should be prefered
+ **/
+char *path_resolve_realpath(char *buf, size_t size, bool resolve_symlinks);
+
+/**
+ * path_relative_to:
+ * @out                : buffer to write the relative path to
+ * @path               : path to be expressed relatively
+ * @base               : relative to this
+ * @size               : size of output buffer
+ *
+ * Turns @path into a path relative to @base and writes it to @out.
+ *
+ * @base is assumed to be a base directory, i.e. a path ending with '/' or '\'.
+ * Both @path and @base are assumed to be absolute paths without "." or "..".
+ *
+ * E.g. path /a/b/e/f.cgp with base /a/b/c/d/ turns into ../../e/f.cgp
+ **/
+size_t path_relative_to(char *out, const char *path, const char *base, size_t size);
+
+/**
+ * path_is_absolute:
+ * @path               : path
+ *
+ * Checks if @path is an absolute path or a relative path.
+ *
+ * Returns: true if path is absolute, false if path is relative.
+ **/
+bool path_is_absolute(const char *path);
+
+/**
+ * fill_pathname:
+ * @out_path           : output path
+ * @in_path            : input  path
+ * @replace            : what to replace
+ * @size               : buffer size of output path
+ *
+ * FIXME: Verify
+ *
+ * Replaces filename extension with 'replace' and outputs result to out_path.
+ * The extension here is considered to be the string from the last '.'
+ * to the end.
+ *
+ * Only '.'s after the last slash are considered as extensions.
+ * If no '.' is present, in_path and replace will simply be concatenated.
+ * 'size' is buffer size of 'out_path'.
+ * E.g.: in_path = "/foo/bar/baz/boo.c", replace = ".asm" =>
+ * out_path = "/foo/bar/baz/boo.asm"
+ * E.g.: in_path = "/foo/bar/baz/boo.c", replace = ""     =>
+ * out_path = "/foo/bar/baz/boo"
+ */
+void fill_pathname(char *out_path, const char *in_path,
+      const char *replace, size_t size);
+
+/**
+ * fill_dated_filename:
+ * @out_filename       : output filename
+ * @ext                : extension of output filename
+ * @size               : buffer size of output filename
+ *
+ * Creates a 'dated' filename prefixed by 'RetroArch', and
+ * concatenates extension (@ext) to it.
+ *
+ * E.g.:
+ * out_filename = "RetroArch-{month}{day}-{Hours}{Minutes}.{@ext}"
+ **/
+size_t fill_dated_filename(char *out_filename,
+      const char *ext, size_t size);
+
+/**
+ * fill_str_dated_filename:
+ * @out_filename       : output filename
+ * @in_str             : input string
+ * @ext                : extension of output filename
+ * @size               : buffer size of output filename
+ *
+ * Creates a 'dated' filename prefixed by the string @in_str, and
+ * concatenates extension (@ext) to it.
+ *
+ * E.g.:
+ * out_filename = "RetroArch-{year}{month}{day}-{Hour}{Minute}{Second}.{@ext}"
+ **/
+void fill_str_dated_filename(char *out_filename,
+      const char *in_str, const char *ext, size_t size);
+
+/**
+ * fill_pathname_noext:
+ * @out_path           : output path
+ * @in_path            : input  path
+ * @replace            : what to replace
+ * @size               : buffer size of output path
+ *
+ * Appends a filename extension 'replace' to 'in_path', and outputs
+ * result in 'out_path'.
+ *
+ * Assumes in_path has no extension. If an extension is still
+ * present in 'in_path', it will be ignored.
+ *
+ */
+size_t fill_pathname_noext(char *out_path, const char *in_path,
+      const char *replace, size_t size);
+
+/**
+ * find_last_slash:
+ * @str : input path
+ *
+ * Gets a pointer to the last slash in the input path.
+ *
+ * Returns: a pointer to the last slash in the input path.
+ **/
+char *find_last_slash(const char *str);
+
+/**
+ * fill_pathname_dir:
+ * @in_dir             : input directory path
+ * @in_basename        : input basename to be appended to @in_dir
+ * @replace            : replacement to be appended to @in_basename
+ * @size               : size of buffer
+ *
+ * Appends basename of 'in_basename', to 'in_dir', along with 'replace'.
+ * Basename of in_basename is the string after the last '/' or '\\',
+ * i.e the filename without directories.
+ *
+ * If in_basename has no '/' or '\\', the whole 'in_basename' will be used.
+ * 'size' is buffer size of 'in_dir'.
+ *
+ * E.g..: in_dir = "/tmp/some_dir", in_basename = "/some_content/foo.c",
+ * replace = ".asm" => in_dir = "/tmp/some_dir/foo.c.asm"
+ **/
+size_t fill_pathname_dir(char *in_dir, const char *in_basename,
+      const char *replace, size_t size);
+
+/**
+ * fill_pathname_base:
+ * @out                : output path
+ * @in_path            : input path
+ * @size               : size of output path
+ *
+ * Copies basename of @in_path into @out_path.
+ **/
+size_t fill_pathname_base(char *out_path, const char *in_path, size_t size);
+
+void fill_pathname_base_noext(char *out_dir,
+      const char *in_path, size_t size);
+
+size_t fill_pathname_base_ext(char *out,
+      const char *in_path, const char *ext,
+      size_t size);
+
+/**
+ * fill_pathname_basedir:
+ * @out_dir            : output directory
+ * @in_path            : input path
+ * @size               : size of output directory
+ *
+ * Copies base directory of @in_path into @out_path.
+ * If in_path is a path without any slashes (relative current directory),
+ * @out_path will get path "./".
+ **/
+void fill_pathname_basedir(char *out_path, const char *in_path, size_t size);
+
+void fill_pathname_basedir_noext(char *out_dir,
+      const char *in_path, size_t size);
+
+/**
+ * fill_pathname_parent_dir_name:
+ * @out_dir            : output directory
+ * @in_dir             : input directory
+ * @size               : size of output directory
+ *
+ * Copies only the parent directory name of @in_dir into @out_dir.
+ * The two buffers must not overlap. Removes trailing '/'.
+ * Returns true on success, false if a slash was not found in the path.
+ **/
+bool fill_pathname_parent_dir_name(char *out_dir,
+      const char *in_dir, size_t size);
+
+/**
+ * fill_pathname_parent_dir:
+ * @out_dir            : output directory
+ * @in_dir             : input directory
+ * @size               : size of output directory
+ *
+ * Copies parent directory of @in_dir into @out_dir.
+ * Assumes @in_dir is a directory. Keeps trailing '/'.
+ * If the path was already at the root directory, @out_dir will be an empty string.
+ **/
+void fill_pathname_parent_dir(char *out_dir,
+      const char *in_dir, size_t size);
+
+/**
+ * fill_pathname_resolve_relative:
+ * @out_path           : output path
+ * @in_refpath         : input reference path
+ * @in_path            : input path
+ * @size               : size of @out_path
+ *
+ * Joins basedir of @in_refpath together with @in_path.
+ * If @in_path is an absolute path, out_path = in_path.
+ * E.g.: in_refpath = "/foo/bar/baz.a", in_path = "foobar.cg",
+ * out_path = "/foo/bar/foobar.cg".
+ **/
+void fill_pathname_resolve_relative(char *out_path, const char *in_refpath,
+      const char *in_path, size_t size);
+
+/**
+ * fill_pathname_join:
+ * @out_path           : output path
+ * @dir                : directory
+ * @path               : path
+ * @size               : size of output path
+ *
+ * Joins a directory (@dir) and path (@path) together.
+ * Makes sure not to get  two consecutive slashes
+ * between directory and path.
+ **/
+size_t fill_pathname_join(char *out_path, const char *dir,
+      const char *path, size_t size);
+
+size_t fill_pathname_join_special_ext(char *out_path,
+      const char *dir,  const char *path,
+      const char *last, const char *ext,
+      size_t size);
+
+size_t fill_pathname_join_concat_noext(char *out_path,
+      const char *dir, const char *path,
+      const char *concat,
+      size_t size);
+
+size_t fill_pathname_join_concat(char *out_path,
+      const char *dir, const char *path,
+      const char *concat,
+      size_t size);
+
+void fill_pathname_join_noext(char *out_path,
+      const char *dir, const char *path, size_t size);
+
+/**
+ * fill_pathname_join_delim:
+ * @out_path           : output path
+ * @dir                : directory
+ * @path               : path
+ * @delim              : delimiter
+ * @size               : size of output path
+ *
+ * Joins a directory (@dir) and path (@path) together
+ * using the given delimiter (@delim).
+ **/
+size_t fill_pathname_join_delim(char *out_path, const char *dir,
+      const char *path, const char delim, size_t size);
+
+size_t fill_pathname_join_delim_concat(char *out_path, const char *dir,
+      const char *path, const char delim, const char *concat,
+      size_t size);
+
+/**
+ * fill_short_pathname_representation:
+ * @out_rep            : output representation
+ * @in_path            : input path
+ * @size               : size of output representation
+ *
+ * Generates a short representation of path. It should only
+ * be used for displaying the result; the output representation is not
+ * binding in any meaningful way (for a normal path, this is the same as basename)
+ * In case of more complex URLs, this should cut everything except for
+ * the main image file.
+ *
+ * E.g.: "/path/to/game.img" -> game.img
+ *       "/path/to/myarchive.7z#folder/to/game.img" -> game.img
+ */
+size_t fill_short_pathname_representation(char* out_rep,
+      const char *in_path, size_t size);
+
+void fill_short_pathname_representation_noext(char* out_rep,
+      const char *in_path, size_t size);
+
+void fill_pathname_expand_special(char *out_path,
+      const char *in_path, size_t size);
+
+void fill_pathname_abbreviate_special(char *out_path,
+      const char *in_path, size_t size);
+
+/**
+ * path_basedir:
+ * @path               : path
+ *
+ * Extracts base directory by mutating path.
+ * Keeps trailing '/'.
+ **/
+void path_basedir_wrapper(char *path);
+
+/**
+ * path_char_is_slash:
+ * @c                  : character
+ *
+ * Checks if character (@c) is a slash.
+ *
+ * Returns: true (1) if character is a slash, otherwise false (0).
+ */
+#ifdef _WIN32
+#define PATH_CHAR_IS_SLASH(c) (((c) == '/') || ((c) == '\\'))
+#else
+#define PATH_CHAR_IS_SLASH(c) ((c) == '/')
+#endif
+
+/**
+ * path_default_slash and path_default_slash_c:
+ *
+ * Gets the default slash separator.
+ *
+ * Returns: default slash separator.
+ */
+#ifdef _WIN32
+#define PATH_DEFAULT_SLASH() "\\"
+#define PATH_DEFAULT_SLASH_C() '\\'
+#else
+#define PATH_DEFAULT_SLASH() "/"
+#define PATH_DEFAULT_SLASH_C() '/'
+#endif
+
+/**
+ * fill_pathname_slash:
+ * @path               : path
+ * @size               : size of path
+ *
+ * Assumes path is a directory. Appends a slash
+ * if not already there.
+ **/
+void fill_pathname_slash(char *path, size_t size);
+
+#if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL)
+void fill_pathname_application_path(char *buf, size_t size);
+void fill_pathname_application_dir(char *buf, size_t size);
+void fill_pathname_home_dir(char *buf, size_t size);
+#endif
+
+/**
+ * path_mkdir:
+ * @dir                : directory
+ *
+ * Create directory on filesystem.
+ *
+ * Returns: true (1) if directory could be created, otherwise false (0).
+ **/
+bool path_mkdir(const char *dir);
+
+/**
+ * path_is_directory:
+ * @path               : path
+ *
+ * Checks if path is a directory.
+ *
+ * Returns: true (1) if path is a directory, otherwise false (0).
+ */
+bool path_is_directory(const char *path);
+
+bool path_is_character_special(const char *path);
+
+int path_stat(const char *path);
+
+bool path_is_valid(const char *path);
+
+int32_t path_get_size(const char *path);
+
+bool is_path_accessible_using_standard_io(const char *path);
+
+RETRO_END_DECLS
+
+#endif
diff --git a/libretro-common/include/memmap.h b/libretro-common/include/memmap.h
new file mode 100644 (file)
index 0000000..75a85f3
--- /dev/null
@@ -0,0 +1,52 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (memmap.h).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef _LIBRETRO_MEMMAP_H
+#define _LIBRETRO_MEMMAP_H
+
+#include <stdio.h>
+#include <stdint.h>
+
+#if defined(__CELLOS_LV2__) || defined(PSP) || defined(PS2) || defined(GEKKO) || defined(VITA) || defined(_XBOX) || defined(_3DS) || defined(WIIU) || defined(SWITCH) || defined(HAVE_LIBNX)
+/* No mman available */
+#elif defined(_WIN32) && !defined(_XBOX)
+#include <windows.h>
+#include <errno.h>
+#include <io.h>
+#else
+#define HAVE_MMAN
+#include <sys/mman.h>
+#endif
+
+#if !defined(HAVE_MMAN) || defined(_WIN32)
+void* mmap(void *addr, size_t len, int mmap_prot, int mmap_flags, int fildes, size_t off);
+
+int munmap(void *addr, size_t len);
+
+int mprotect(void *addr, size_t len, int prot);
+#endif
+
+int memsync(void *start, void *end);
+
+int memprotect(void *addr, size_t len);
+
+#endif
diff --git a/libretro-common/include/retro_assert.h b/libretro-common/include/retro_assert.h
new file mode 100644 (file)
index 0000000..090e9f1
--- /dev/null
@@ -0,0 +1,37 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (retro_assert.h).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __RETRO_ASSERT_H
+#define __RETRO_ASSERT_H
+
+#include <assert.h>
+
+#ifdef RARCH_INTERNAL
+#include <stdio.h>
+#define retro_assert(cond) do { \
+   if (!(cond)) { printf("Assertion failed at %s:%d.\n", __FILE__, __LINE__); abort(); } \
+} while(0)
+#else
+#define retro_assert(cond) assert(cond)
+#endif
+
+#endif
diff --git a/libretro-common/include/retro_environment.h b/libretro-common/include/retro_environment.h
new file mode 100644 (file)
index 0000000..1389eb5
--- /dev/null
@@ -0,0 +1,114 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (retro_environment.h).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __LIBRETRO_SDK_ENVIRONMENT_H
+#define __LIBRETRO_SDK_ENVIRONMENT_H
+
+/*
+This file is designed to create a normalized environment for compiling
+libretro-common's private implementations, or any other sources which might
+enjoy use of it's environment (RetroArch for instance).
+This should be an elaborately crafted environment so that sources don't
+need to be full of platform-specific workarounds.
+*/
+
+#if defined (__cplusplus)
+#if 0
+printf("This is C++, version %d.\n", __cplusplus);
+#endif
+/* The expected values would be
+ *   199711L, for ISO/IEC 14882:1998 or 14882:2003
+ */
+
+#elif defined(__STDC__)
+/* This is standard C. */
+
+#if (__STDC__ == 1)
+/* The implementation is ISO-conforming. */
+#define __STDC_ISO__
+#else
+/* The implementation is not ISO-conforming. */
+#endif
+
+#if defined(__STDC_VERSION__)
+#if (__STDC_VERSION__ >= 201112L)
+/* This is C11. */
+#define __STDC_C11__
+#elif (__STDC_VERSION__ >= 199901L)
+/* This is C99. */
+#define __STDC_C99__
+#elif (__STDC_VERSION__ >= 199409L)
+/* This is C89 with amendment 1. */
+#define __STDC_C89__
+#define __STDC_C89_AMENDMENT_1__
+#else
+/* This is C89 without amendment 1. */
+#define __STDC_C89__
+#endif
+#else /* !defined(__STDC_VERSION__) */
+/* This is C89. __STDC_VERSION__ is not defined. */
+#define __STDC_C89__
+#endif
+
+#else   /* !defined(__STDC__) */
+/* This is not standard C. __STDC__ is not defined. */
+#endif
+
+#if defined(WIN32) || defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__)
+/* Try to find out if we're compiling for WinRT or non-WinRT */
+#if defined(_MSC_VER) && defined(__has_include)
+#if __has_include(<winapifamily.h>)
+#define HAVE_WINAPIFAMILY_H 1
+#else
+#define HAVE_WINAPIFAMILY_H 0
+#endif
+
+/* If _USING_V110_SDK71_ is defined it means we are using the Windows XP toolset. */
+#elif defined(_MSC_VER) && (_MSC_VER >= 1700 && !_USING_V110_SDK71_)    /* _MSC_VER == 1700 for Visual Studio 2012 */
+#define HAVE_WINAPIFAMILY_H 1
+#else
+#define HAVE_WINAPIFAMILY_H 0
+#endif
+
+#if HAVE_WINAPIFAMILY_H
+#include <winapifamily.h>
+#define WINAPI_FAMILY_WINRT (!WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP))
+#else
+#define WINAPI_FAMILY_WINRT 0
+#endif /* HAVE_WINAPIFAMILY_H */
+
+#if WINAPI_FAMILY_WINRT
+#undef __WINRT__
+#define __WINRT__ 1
+#endif
+
+/* MSVC obviously has to have some non-standard constants... */
+#if _M_IX86_FP == 1
+#define __SSE__ 1
+#elif _M_IX86_FP == 2 || (defined(_M_AMD64) || defined(_M_X64))
+#define __SSE__ 1
+#define __SSE2__ 1
+#endif
+
+#endif
+
+#endif
diff --git a/libretro-common/include/retro_miscellaneous.h b/libretro-common/include/retro_miscellaneous.h
new file mode 100644 (file)
index 0000000..1936ac7
--- /dev/null
@@ -0,0 +1,186 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (retro_miscellaneous.h).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __RARCH_MISCELLANEOUS_H
+#define __RARCH_MISCELLANEOUS_H
+
+#define RARCH_MAX_SUBSYSTEMS 10
+#define RARCH_MAX_SUBSYSTEM_ROMS 10
+
+#include <stdint.h>
+#include <boolean.h>
+#include <retro_inline.h>
+
+#if defined(_WIN32)
+
+#if defined(_XBOX)
+#include <Xtl.h>
+#else
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#endif
+
+#endif
+
+#if defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
+#include <sys/fs_external.h>
+#endif
+
+#include <limits.h>
+
+#ifdef _MSC_VER
+#include <compat/msvc.h>
+#endif
+
+static INLINE void bits_or_bits(uint32_t *a, uint32_t *b, uint32_t count)
+{
+   uint32_t i;
+   for (i = 0; i < count;i++)
+      a[i] |= b[i];
+}
+
+static INLINE void bits_clear_bits(uint32_t *a, uint32_t *b, uint32_t count)
+{
+   uint32_t i;
+   for (i = 0; i < count;i++)
+      a[i] &= ~b[i];
+}
+
+static INLINE bool bits_any_set(uint32_t* ptr, uint32_t count)
+{
+   uint32_t i;
+   for (i = 0; i < count; i++)
+   {
+      if (ptr[i] != 0)
+         return true;
+   }
+   return false;
+}
+
+#ifndef PATH_MAX_LENGTH
+#if defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
+#define PATH_MAX_LENGTH CELL_FS_MAX_FS_PATH_LENGTH
+#elif defined(_XBOX1) || defined(_3DS) || defined(PSP) || defined(PS2) || defined(GEKKO)|| defined(WIIU) || defined(ORBIS)
+#define PATH_MAX_LENGTH 512
+#else
+#define PATH_MAX_LENGTH 4096
+#endif
+#endif
+
+#ifndef MAX
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#endif
+
+#ifndef MIN
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+#define ARRAY_SIZE(a)              (sizeof(a) / sizeof((a)[0]))
+
+#define BITS_GET_ELEM(a, i)        ((a).data[i])
+#define BITS_GET_ELEM_PTR(a, i)    ((a)->data[i])
+
+#define BIT_SET(a, bit)   ((a)[(bit) >> 3] |=  (1 << ((bit) & 7)))
+#define BIT_CLEAR(a, bit) ((a)[(bit) >> 3] &= ~(1 << ((bit) & 7)))
+#define BIT_GET(a, bit)   (((a)[(bit) >> 3] >> ((bit) & 7)) & 1)
+
+#define BIT16_SET(a, bit)    ((a) |=  (1 << ((bit) & 15)))
+#define BIT16_CLEAR(a, bit)  ((a) &= ~(1 << ((bit) & 15)))
+#define BIT16_GET(a, bit)    (((a) >> ((bit) & 15)) & 1)
+#define BIT16_CLEAR_ALL(a)   ((a) = 0)
+
+#define BIT32_SET(a, bit)    ((a) |=  (UINT32_C(1) << ((bit) & 31)))
+#define BIT32_CLEAR(a, bit)  ((a) &= ~(UINT32_C(1) << ((bit) & 31)))
+#define BIT32_GET(a, bit)    (((a) >> ((bit) & 31)) & 1)
+#define BIT32_CLEAR_ALL(a)   ((a) = 0)
+
+#define BIT64_SET(a, bit)    ((a) |=  (UINT64_C(1) << ((bit) & 63)))
+#define BIT64_CLEAR(a, bit)  ((a) &= ~(UINT64_C(1) << ((bit) & 63)))
+#define BIT64_GET(a, bit)    (((a) >> ((bit) & 63)) & 1)
+#define BIT64_CLEAR_ALL(a)   ((a) = 0)
+
+#define BIT128_SET(a, bit)   ((a).data[(bit) >> 5] |=  (UINT32_C(1) << ((bit) & 31)))
+#define BIT128_CLEAR(a, bit) ((a).data[(bit) >> 5] &= ~(UINT32_C(1) << ((bit) & 31)))
+#define BIT128_GET(a, bit)   (((a).data[(bit) >> 5] >> ((bit) & 31)) & 1)
+#define BIT128_CLEAR_ALL(a)  memset(&(a), 0, sizeof(a))
+
+#define BIT128_SET_PTR(a, bit)   BIT128_SET(*a, bit)
+#define BIT128_CLEAR_PTR(a, bit) BIT128_CLEAR(*a, bit)
+#define BIT128_GET_PTR(a, bit)   BIT128_GET(*a, bit)
+#define BIT128_CLEAR_ALL_PTR(a)  BIT128_CLEAR_ALL(*a)
+
+#define BIT256_SET(a, bit)       BIT128_SET(a, bit)
+#define BIT256_CLEAR(a, bit)     BIT128_CLEAR(a, bit)
+#define BIT256_GET(a, bit)       BIT128_GET(a, bit)
+#define BIT256_CLEAR_ALL(a)      BIT128_CLEAR_ALL(a)
+
+#define BIT256_SET_PTR(a, bit)   BIT256_SET(*a, bit)
+#define BIT256_CLEAR_PTR(a, bit) BIT256_CLEAR(*a, bit)
+#define BIT256_GET_PTR(a, bit)   BIT256_GET(*a, bit)
+#define BIT256_CLEAR_ALL_PTR(a)  BIT256_CLEAR_ALL(*a)
+
+#define BITS_COPY16_PTR(a,bits) \
+{ \
+   BIT128_CLEAR_ALL_PTR(a); \
+   BITS_GET_ELEM_PTR(a, 0) = (bits) & 0xffff; \
+}
+
+#define BITS_COPY32_PTR(a,bits) \
+{ \
+   BIT128_CLEAR_ALL_PTR(a); \
+   BITS_GET_ELEM_PTR(a, 0) = (bits); \
+}
+
+/* Helper macros and struct to keep track of many booleans. */
+/* This struct has 256 bits. */
+typedef struct
+{
+   uint32_t data[8];
+} retro_bits_t;
+
+#ifdef _WIN32
+#  ifdef _WIN64
+#    define PRI_SIZET PRIu64
+#  else
+#    if _MSC_VER == 1800
+#      define PRI_SIZET PRIu32
+#    else
+#      define PRI_SIZET "u"
+#    endif
+#  endif
+#elif defined(PS2)
+#  define PRI_SIZET "u"
+#else
+#  if (SIZE_MAX == 0xFFFF)
+#    define PRI_SIZET "hu"
+#  elif (SIZE_MAX == 0xFFFFFFFF)
+#    define PRI_SIZET "u"
+#  elif (SIZE_MAX == 0xFFFFFFFFFFFFFFFF)
+#    define PRI_SIZET "lu"
+#  else
+#    error PRI_SIZET: unknown SIZE_MAX
+#  endif
+#endif
+
+#endif
diff --git a/libretro-common/include/streams/file_stream.h b/libretro-common/include/streams/file_stream.h
new file mode 100644 (file)
index 0000000..ab198b2
--- /dev/null
@@ -0,0 +1,115 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (file_stream.h).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __LIBRETRO_SDK_FILE_STREAM_H
+#define __LIBRETRO_SDK_FILE_STREAM_H
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stddef.h>
+
+#include <sys/types.h>
+
+#include <libretro.h>
+#include <retro_common_api.h>
+#include <retro_inline.h>
+#include <boolean.h>
+
+#include <stdarg.h>
+#include <vfs/vfs_implementation.h>
+
+#define FILESTREAM_REQUIRED_VFS_VERSION 2
+
+RETRO_BEGIN_DECLS
+
+typedef struct RFILE RFILE;
+
+#define FILESTREAM_REQUIRED_VFS_VERSION 2
+
+void filestream_vfs_init(const struct retro_vfs_interface_info* vfs_info);
+
+int64_t filestream_get_size(RFILE *stream);
+
+int64_t filestream_truncate(RFILE *stream, int64_t length);
+
+/**
+ * filestream_open:
+ * @path               : path to file
+ * @mode               : file mode to use when opening (read/write)
+ * @bufsize            : optional buffer size (-1 or 0 to use default)
+ *
+ * Opens a file for reading or writing, depending on the requested mode.
+ * Returns a pointer to an RFILE if opened successfully, otherwise NULL.
+ **/
+RFILE* filestream_open(const char *path, unsigned mode, unsigned hints);
+
+int64_t filestream_seek(RFILE *stream, int64_t offset, int seek_position);
+
+int64_t filestream_read(RFILE *stream, void *data, int64_t len);
+
+int64_t filestream_write(RFILE *stream, const void *data, int64_t len);
+
+int64_t filestream_tell(RFILE *stream);
+
+void filestream_rewind(RFILE *stream);
+
+int filestream_close(RFILE *stream);
+
+int64_t filestream_read_file(const char *path, void **buf, int64_t *len);
+
+char* filestream_gets(RFILE *stream, char *s, size_t len);
+
+int filestream_getc(RFILE *stream);
+
+int filestream_scanf(RFILE *stream, const char* format, ...);
+
+int filestream_eof(RFILE *stream);
+
+bool filestream_write_file(const char *path, const void *data, int64_t size);
+
+int filestream_putc(RFILE *stream, int c);
+
+int filestream_vprintf(RFILE *stream, const char* format, va_list args);
+
+int filestream_printf(RFILE *stream, const char* format, ...);
+
+int filestream_error(RFILE *stream);
+
+int filestream_flush(RFILE *stream);
+
+int filestream_delete(const char *path);
+
+int filestream_rename(const char *old_path, const char *new_path);
+
+const char* filestream_get_path(RFILE *stream);
+
+bool filestream_exists(const char *path);
+
+/* Returned pointer must be freed by the caller. */
+char* filestream_getline(RFILE *stream);
+
+libretro_vfs_implementation_file* filestream_get_vfs_handle(RFILE *stream);
+
+RETRO_END_DECLS
+
+#endif
diff --git a/libretro-common/include/streams/file_stream_transforms.h b/libretro-common/include/streams/file_stream_transforms.h
new file mode 100644 (file)
index 0000000..327e218
--- /dev/null
@@ -0,0 +1,101 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+*
+* ---------------------------------------------------------------------------------------
+* The following license statement only applies to this file (file_stream_transforms.h).
+* ---------------------------------------------------------------------------------------
+*
+* Permission is hereby granted, free of charge,
+* to any person obtaining a copy of this software and associated documentation files (the "Software"),
+* to deal in the Software without restriction, including without limitation the rights to
+* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef __LIBRETRO_SDK_FILE_STREAM_TRANSFORMS_H
+#define __LIBRETRO_SDK_FILE_STREAM_TRANSFORMS_H
+
+#include <stdint.h>
+#include <string.h>
+#include <retro_common_api.h>
+#include <streams/file_stream.h>
+
+RETRO_BEGIN_DECLS
+
+#ifndef SKIP_STDIO_REDEFINES
+
+#define FILE RFILE
+
+#undef fopen
+#undef fclose
+#undef ftell
+#undef fseek
+#undef fread
+#undef fgets
+#undef fgetc
+#undef fwrite
+#undef fputc
+#undef fflush
+#undef fprintf
+#undef ferror
+#undef feof
+#undef fscanf
+
+#define fopen rfopen
+#define fclose rfclose
+#define ftell rftell
+#define fseek rfseek
+#define fread rfread
+#define fgets rfgets
+#define fgetc rfgetc
+#define fwrite rfwrite
+#define fputc rfputc
+#define fflush rfflush
+#define fprintf rfprintf
+#define ferror rferror
+#define feof rfeof
+#define fscanf rfscanf
+
+#endif
+
+RFILE* rfopen(const char *path, const char *mode);
+
+int rfclose(RFILE* stream);
+
+int64_t rftell(RFILE* stream);
+
+int64_t rfseek(RFILE* stream, int64_t offset, int origin);
+
+int64_t rfread(void* buffer,
+   size_t elem_size, size_t elem_count, RFILE* stream);
+
+char *rfgets(char *buffer, int maxCount, RFILE* stream);
+
+int rfgetc(RFILE* stream);
+
+int64_t rfwrite(void const* buffer,
+   size_t elem_size, size_t elem_count, RFILE* stream);
+
+int rfputc(int character, RFILE * stream);
+
+int64_t rfflush(RFILE * stream);
+
+int rfprintf(RFILE * stream, const char * format, ...);
+
+int rferror(RFILE* stream);
+
+int rfeof(RFILE* stream);
+
+int rfscanf(RFILE * stream, const char * format, ...);
+
+RETRO_END_DECLS
+
+#endif
diff --git a/libretro-common/include/string/stdstring.h b/libretro-common/include/string/stdstring.h
new file mode 100644 (file)
index 0000000..5d8c31b
--- /dev/null
@@ -0,0 +1,198 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (stdstring.h).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __LIBRETRO_SDK_STDSTRING_H
+#define __LIBRETRO_SDK_STDSTRING_H
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <string.h>
+#include <boolean.h>
+
+#include <retro_common_api.h>
+#include <retro_inline.h>
+#include <compat/strl.h>
+
+RETRO_BEGIN_DECLS
+
+#define STRLEN_CONST(x)                   ((sizeof((x))-1))
+
+#define strcpy_literal(a, b)              strcpy(a, b)
+
+#define string_is_not_equal(a, b)         !string_is_equal((a), (b))
+
+#define string_is_not_equal_fast(a, b, size) (memcmp(a, b, size) != 0)
+#define string_is_equal_fast(a, b, size)     (memcmp(a, b, size) == 0)
+
+#define TOLOWER(c)   (c |  (lr_char_props[c] & 0x20))
+#define TOUPPER(c)   (c & ~(lr_char_props[c] & 0x20))
+
+/* C standard says \f \v are space, but this one disagrees */
+#define ISSPACE(c)   (lr_char_props[c]  & 0x80) 
+
+#define ISDIGIT(c)   (lr_char_props[c]  & 0x40)
+#define ISALPHA(c)   (lr_char_props[c]  & 0x20)
+#define ISLOWER(c)   (lr_char_props[c]  & 0x04)
+#define ISUPPER(c)   (lr_char_props[c]  & 0x02)
+#define ISALNUM(c)   (lr_char_props[c]  & 0x60)
+#define ISUALPHA(c)  (lr_char_props[c]  & 0x28)
+#define ISUALNUM(c)  (lr_char_props[c]  & 0x68)
+#define IS_XDIGIT(c) (lr_char_props[c]  & 0x01)
+
+/* Deprecated alias, all callers should use string_is_equal_case_insensitive instead */
+#define string_is_equal_noncase string_is_equal_case_insensitive
+
+static INLINE bool string_is_empty(const char *data)
+{
+   return !data || (*data == '\0');
+}
+
+static INLINE bool string_is_equal(const char *a, const char *b)
+{
+   return (a && b) ? !strcmp(a, b) : false;
+}
+
+static INLINE bool string_starts_with_size(const char *str, const char *prefix,
+      size_t size)
+{
+   return (str && prefix) ? !strncmp(prefix, str, size) : false;
+}
+
+static INLINE bool string_starts_with(const char *str, const char *prefix)
+{
+   return (str && prefix) ? !strncmp(prefix, str, strlen(prefix)) : false;
+}
+
+static INLINE bool string_ends_with_size(const char *str, const char *suffix,
+      size_t str_len, size_t suffix_len)
+{
+   return (str_len < suffix_len) ? false :
+         !memcmp(suffix, str + (str_len - suffix_len), suffix_len);
+}
+
+static INLINE bool string_ends_with(const char *str, const char *suffix)
+{
+   if (!str || !suffix)
+      return false;
+   return string_ends_with_size(str, suffix, strlen(str), strlen(suffix));
+}
+
+/* Returns the length of 'str' (c.f. strlen()), but only
+ * checks the first 'size' characters
+ * - If 'str' is NULL, returns 0
+ * - If 'str' is not NULL and no '\0' character is found
+ *   in the first 'size' characters, returns 'size' */
+static INLINE size_t strlen_size(const char *str, size_t size)
+{
+   size_t i = 0;
+   if (str)
+      while (i < size && str[i]) i++;
+   return i;
+}
+
+
+static INLINE bool string_is_equal_case_insensitive(const char *a,
+      const char *b)
+{
+   int result              = 0;
+   const unsigned char *p1 = (const unsigned char*)a;
+   const unsigned char *p2 = (const unsigned char*)b;
+
+   if (!a || !b)
+      return false;
+   if (p1 == p2)
+      return true;
+
+   while ((result = tolower (*p1) - tolower (*p2++)) == 0)
+      if (*p1++ == '\0')
+         break;
+
+   return (result == 0);
+}
+
+char *string_to_upper(char *s);
+
+char *string_to_lower(char *s);
+
+char *string_ucwords(char *s);
+
+char *string_replace_substring(const char *in, const char *pattern,
+      const char *by);
+
+/* Remove leading whitespaces */
+char *string_trim_whitespace_left(char *const s);
+
+/* Remove trailing whitespaces */
+char *string_trim_whitespace_right(char *const s);
+
+/* Remove leading and trailing whitespaces */
+char *string_trim_whitespace(char *const s);
+
+/* max_lines == 0 means no limit */
+char *word_wrap(char *buffer, const char *string,
+      int line_width, bool unicode, unsigned max_lines);
+
+/* Splits string into tokens seperated by 'delim'
+ * > Returned token string must be free()'d
+ * > Returns NULL if token is not found
+ * > After each call, 'str' is set to the position after the
+ *   last found token
+ * > Tokens *include* empty strings
+ * Usage example:
+ *    char *str      = "1,2,3,4,5,6,7,,,10,";
+ *    char **str_ptr = &str;
+ *    char *token    = NULL;
+ *    while ((token = string_tokenize(str_ptr, ",")))
+ *    {
+ *        printf("%s\n", token);
+ *        free(token);
+ *        token = NULL;
+ *    }
+ */
+char* string_tokenize(char **str, const char *delim);
+
+/* Removes every instance of character 'c' from 'str' */
+void string_remove_all_chars(char *str, char c);
+
+/* Replaces every instance of character 'find' in 'str'
+ * with character 'replace' */
+void string_replace_all_chars(char *str, char find, char replace);
+
+/* Converts string to unsigned integer.
+ * Returns 0 if string is invalid  */
+unsigned string_to_unsigned(const char *str);
+
+/* Converts hexadecimal string to unsigned integer.
+ * Handles optional leading '0x'.
+ * Returns 0 if string is invalid  */
+unsigned string_hex_to_unsigned(const char *str);
+
+char *string_init(const char *src);
+
+void string_set(char **string, const char *src);
+
+extern const unsigned char lr_char_props[256];
+
+RETRO_END_DECLS
+
+#endif
diff --git a/libretro-common/include/time/rtime.h b/libretro-common/include/time/rtime.h
new file mode 100644 (file)
index 0000000..7629c1e
--- /dev/null
@@ -0,0 +1,48 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (rtime.h).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __LIBRETRO_SDK_RTIME_H__
+#define __LIBRETRO_SDK_RTIME_H__
+
+#include <retro_common_api.h>
+
+#include <stdint.h>
+#include <stddef.h>
+#include <time.h>
+
+RETRO_BEGIN_DECLS
+
+/* TODO/FIXME: Move all generic time handling functions
+ * to this file */
+
+/* Must be called before using rtime_localtime() */
+void rtime_init(void);
+
+/* Must be called upon program termination */
+void rtime_deinit(void);
+
+/* Thread-safe wrapper for localtime() */
+struct tm *rtime_localtime(const time_t *timep, struct tm *result);
+
+RETRO_END_DECLS
+
+#endif
diff --git a/libretro-common/include/vfs/vfs.h b/libretro-common/include/vfs/vfs.h
new file mode 100644 (file)
index 0000000..5366f33
--- /dev/null
@@ -0,0 +1,111 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+*
+* ---------------------------------------------------------------------------------------
+* The following license statement only applies to this file (vfs_implementation.h).
+* ---------------------------------------------------------------------------------------
+*
+* Permission is hereby granted, free of charge,
+* to any person obtaining a copy of this software and associated documentation files (the "Software"),
+* to deal in the Software without restriction, including without limitation the rights to
+* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef __LIBRETRO_SDK_VFS_H
+#define __LIBRETRO_SDK_VFS_H
+
+#include <retro_common_api.h>
+#include <boolean.h>
+
+#ifdef RARCH_INTERNAL
+#ifndef VFS_FRONTEND
+#define VFS_FRONTEND
+#endif
+#endif
+
+RETRO_BEGIN_DECLS
+
+#ifdef _WIN32
+typedef void* HANDLE;
+#endif
+
+#ifdef HAVE_CDROM
+typedef struct
+{
+   int64_t byte_pos;
+   char *cue_buf;
+   size_t cue_len;
+   unsigned cur_lba;
+   unsigned last_frame_lba;
+   unsigned char cur_min;
+   unsigned char cur_sec;
+   unsigned char cur_frame;
+   unsigned char cur_track;
+   unsigned char last_frame[2352];
+   char drive;
+   bool last_frame_valid;
+} vfs_cdrom_t;
+#endif
+
+enum vfs_scheme
+{
+   VFS_SCHEME_NONE = 0,
+   VFS_SCHEME_CDROM
+};
+
+#ifndef __WINRT__
+#ifdef VFS_FRONTEND
+struct retro_vfs_file_handle
+#else
+struct libretro_vfs_implementation_file
+#endif
+{
+#ifdef HAVE_CDROM
+   vfs_cdrom_t cdrom; /* int64_t alignment */
+#endif
+   int64_t size;
+   uint64_t mappos;
+   uint64_t mapsize;
+   FILE *fp;
+#ifdef _WIN32
+   HANDLE fh;
+#endif
+   char *buf;
+   char* orig_path;
+   uint8_t *mapped;
+   int fd;
+   unsigned hints;
+   enum vfs_scheme scheme;
+};
+#endif
+
+/* Replace the following symbol with something appropriate
+ * to signify the file is being compiled for a front end instead of a core.
+ * This allows the same code to act as reference implementation
+ * for VFS and as fallbacks for when the front end does not provide VFS functionality.
+ */
+
+#ifdef VFS_FRONTEND
+typedef struct retro_vfs_file_handle libretro_vfs_implementation_file;
+#else
+typedef struct libretro_vfs_implementation_file libretro_vfs_implementation_file;
+#endif
+
+#ifdef VFS_FRONTEND
+typedef struct retro_vfs_dir_handle libretro_vfs_implementation_dir;
+#else
+typedef struct libretro_vfs_implementation_dir libretro_vfs_implementation_dir;
+#endif
+
+RETRO_END_DECLS
+
+#endif
diff --git a/libretro-common/include/vfs/vfs_implementation.h b/libretro-common/include/vfs/vfs_implementation.h
new file mode 100644 (file)
index 0000000..c7d6bb0
--- /dev/null
@@ -0,0 +1,76 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+*
+* ---------------------------------------------------------------------------------------
+* The following license statement only applies to this file (vfs_implementation.h).
+* ---------------------------------------------------------------------------------------
+*
+* Permission is hereby granted, free of charge,
+* to any person obtaining a copy of this software and associated documentation files (the "Software"),
+* to deal in the Software without restriction, including without limitation the rights to
+* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef __LIBRETRO_SDK_VFS_IMPLEMENTATION_H
+#define __LIBRETRO_SDK_VFS_IMPLEMENTATION_H
+
+#include <stdio.h>
+#include <stdint.h>
+#include <libretro.h>
+#include <retro_environment.h>
+#include <vfs/vfs.h>
+
+RETRO_BEGIN_DECLS
+
+libretro_vfs_implementation_file *retro_vfs_file_open_impl(const char *path, unsigned mode, unsigned hints);
+
+int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream);
+
+int retro_vfs_file_error_impl(libretro_vfs_implementation_file *stream);
+
+int64_t retro_vfs_file_size_impl(libretro_vfs_implementation_file *stream);
+
+int64_t retro_vfs_file_truncate_impl(libretro_vfs_implementation_file *stream, int64_t length);
+
+int64_t retro_vfs_file_tell_impl(libretro_vfs_implementation_file *stream);
+
+int64_t retro_vfs_file_seek_impl(libretro_vfs_implementation_file *stream, int64_t offset, int seek_position);
+
+int64_t retro_vfs_file_read_impl(libretro_vfs_implementation_file *stream, void *s, uint64_t len);
+
+int64_t retro_vfs_file_write_impl(libretro_vfs_implementation_file *stream, const void *s, uint64_t len);
+
+int retro_vfs_file_flush_impl(libretro_vfs_implementation_file *stream);
+
+int retro_vfs_file_remove_impl(const char *path);
+
+int retro_vfs_file_rename_impl(const char *old_path, const char *new_path);
+
+const char *retro_vfs_file_get_path_impl(libretro_vfs_implementation_file *stream);
+
+int retro_vfs_stat_impl(const char *path, int32_t *size);
+
+int retro_vfs_mkdir_impl(const char *dir);
+
+libretro_vfs_implementation_dir *retro_vfs_opendir_impl(const char *dir, bool include_hidden);
+
+bool retro_vfs_readdir_impl(libretro_vfs_implementation_dir *dirstream);
+
+const char *retro_vfs_dirent_get_name_impl(libretro_vfs_implementation_dir *dirstream);
+
+bool retro_vfs_dirent_is_dir_impl(libretro_vfs_implementation_dir *dirstream);
+
+int retro_vfs_closedir_impl(libretro_vfs_implementation_dir *dirstream);
+
+RETRO_END_DECLS
+
+#endif
diff --git a/libretro-common/streams/file_stream.c b/libretro-common/streams/file_stream.c
new file mode 100644 (file)
index 0000000..ae64c4c
--- /dev/null
@@ -0,0 +1,662 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (file_stream.c).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <errno.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef _MSC_VER
+#include <compat/msvc.h>
+#endif
+
+#include <string/stdstring.h>
+#include <streams/file_stream.h>
+#define VFS_FRONTEND
+#include <vfs/vfs_implementation.h>
+
+#define VFS_ERROR_RETURN_VALUE -1
+
+struct RFILE
+{
+   struct retro_vfs_file_handle *hfile;
+       bool error_flag;
+       bool eof_flag;
+};
+
+static retro_vfs_get_path_t filestream_get_path_cb = NULL;
+static retro_vfs_open_t filestream_open_cb         = NULL;
+static retro_vfs_close_t filestream_close_cb       = NULL;
+static retro_vfs_size_t filestream_size_cb         = NULL;
+static retro_vfs_truncate_t filestream_truncate_cb = NULL;
+static retro_vfs_tell_t filestream_tell_cb         = NULL;
+static retro_vfs_seek_t filestream_seek_cb         = NULL;
+static retro_vfs_read_t filestream_read_cb         = NULL;
+static retro_vfs_write_t filestream_write_cb       = NULL;
+static retro_vfs_flush_t filestream_flush_cb       = NULL;
+static retro_vfs_remove_t filestream_remove_cb     = NULL;
+static retro_vfs_rename_t filestream_rename_cb     = NULL;
+
+/* VFS Initialization */
+
+void filestream_vfs_init(const struct retro_vfs_interface_info* vfs_info)
+{
+   const struct retro_vfs_interface *
+      vfs_iface           = vfs_info->iface;
+
+   filestream_get_path_cb = NULL;
+   filestream_open_cb     = NULL;
+   filestream_close_cb    = NULL;
+   filestream_tell_cb     = NULL;
+   filestream_size_cb     = NULL;
+   filestream_truncate_cb = NULL;
+   filestream_seek_cb     = NULL;
+   filestream_read_cb     = NULL;
+   filestream_write_cb    = NULL;
+   filestream_flush_cb    = NULL;
+   filestream_remove_cb   = NULL;
+   filestream_rename_cb   = NULL;
+
+   if (
+             (vfs_info->required_interface_version < 
+             FILESTREAM_REQUIRED_VFS_VERSION)
+         || !vfs_iface)
+      return;
+
+   filestream_get_path_cb = vfs_iface->get_path;
+   filestream_open_cb     = vfs_iface->open;
+   filestream_close_cb    = vfs_iface->close;
+   filestream_size_cb     = vfs_iface->size;
+   filestream_truncate_cb = vfs_iface->truncate;
+   filestream_tell_cb     = vfs_iface->tell;
+   filestream_seek_cb     = vfs_iface->seek;
+   filestream_read_cb     = vfs_iface->read;
+   filestream_write_cb    = vfs_iface->write;
+   filestream_flush_cb    = vfs_iface->flush;
+   filestream_remove_cb   = vfs_iface->remove;
+   filestream_rename_cb   = vfs_iface->rename;
+}
+
+/* Callback wrappers */
+bool filestream_exists(const char *path)
+{
+   RFILE *dummy           = NULL;
+
+   if (!path || !*path)
+      return false;
+
+   dummy                  = filestream_open(
+         path,
+         RETRO_VFS_FILE_ACCESS_READ,
+         RETRO_VFS_FILE_ACCESS_HINT_NONE);
+
+   if (!dummy)
+      return false;
+
+   if (filestream_close(dummy) != 0)
+      if (dummy)
+         free(dummy);
+
+   dummy = NULL;
+   return true;
+}
+
+int64_t filestream_get_size(RFILE *stream)
+{
+   int64_t output;
+
+   if (filestream_size_cb)
+      output = filestream_size_cb(stream->hfile);
+   else
+      output = retro_vfs_file_size_impl(
+            (libretro_vfs_implementation_file*)stream->hfile);
+
+   if (output == VFS_ERROR_RETURN_VALUE)
+      stream->error_flag = true;
+
+   return output;
+}
+
+int64_t filestream_truncate(RFILE *stream, int64_t length)
+{
+   int64_t output;
+
+   if (filestream_truncate_cb)
+      output = filestream_truncate_cb(stream->hfile, length);
+   else
+      output = retro_vfs_file_truncate_impl(
+            (libretro_vfs_implementation_file*)stream->hfile, length);
+
+   if (output == VFS_ERROR_RETURN_VALUE)
+      stream->error_flag = true;
+
+   return output;
+}
+
+/**
+ * filestream_open:
+ * @path               : path to file
+ * @mode               : file mode to use when opening (read/write)
+ * @hints              :
+ *
+ * Opens a file for reading or writing, depending on the requested mode.
+ * Returns a pointer to an RFILE if opened successfully, otherwise NULL.
+ **/
+RFILE* filestream_open(const char *path, unsigned mode, unsigned hints)
+{
+   struct retro_vfs_file_handle  *fp = NULL;
+   RFILE* output                     = NULL;
+
+   if (filestream_open_cb)
+      fp = (struct retro_vfs_file_handle*)
+         filestream_open_cb(path, mode, hints);
+   else
+      fp = (struct retro_vfs_file_handle*)
+         retro_vfs_file_open_impl(path, mode, hints);
+
+   if (!fp)
+      return NULL;
+
+   output             = (RFILE*)malloc(sizeof(RFILE));
+   output->error_flag = false;
+   output->eof_flag   = false;
+   output->hfile      = fp;
+   return output;
+}
+
+char* filestream_gets(RFILE *stream, char *s, size_t len)
+{
+   int c   = 0;
+   char *p = s;
+   if (!stream)
+      return NULL;
+
+   /* get max bytes or up to a newline */
+
+   for (len--; len > 0; len--)
+   {
+      if ((c = filestream_getc(stream)) == EOF)
+         break;
+      *p++ = c;
+      if (c == '\n')
+         break;
+   }
+   *p = 0;
+
+   if (p == s && c == EOF)
+      return NULL;
+   return (s);
+}
+
+int filestream_getc(RFILE *stream)
+{
+   char c = 0;
+   if (stream && filestream_read(stream, &c, 1) == 1)
+      return (int)(unsigned char)c;
+   return EOF;
+}
+
+int filestream_scanf(RFILE *stream, const char* format, ...)
+{
+   char buf[4096];
+   char subfmt[64];
+   va_list args;
+   const char * bufiter = buf;
+   int        ret       = 0;
+   int64_t startpos     = filestream_tell(stream);
+   int64_t maxlen       = filestream_read(stream, buf, sizeof(buf)-1);
+
+   if (maxlen <= 0)
+      return EOF;
+
+   buf[maxlen] = '\0';
+
+   va_start(args, format);
+
+   while (*format)
+   {
+      if (*format == '%')
+      {
+         int sublen;
+         char* subfmtiter = subfmt;
+         bool asterisk    = false;
+
+         *subfmtiter++    = *format++; /* '%' */
+
+         /* %[*][width][length]specifier */
+
+         if (*format == '*')
+         {
+            asterisk      = true;
+            *subfmtiter++ = *format++;
+         }
+
+         while (ISDIGIT((unsigned char)*format))
+            *subfmtiter++ = *format++; /* width */
+
+         /* length */
+         if (*format == 'h' || *format == 'l')
+         {
+            if (format[1] == format[0])
+               *subfmtiter++ = *format++;
+            *subfmtiter++    = *format++;
+         }
+         else if (
+               *format == 'j' || 
+               *format == 'z' || 
+               *format == 't' || 
+               *format == 'L')
+         {
+            *subfmtiter++ = *format++;
+         }
+
+         /* specifier - always a single character (except ]) */
+         if (*format == '[')
+         {
+            while (*format != ']')
+               *subfmtiter++ = *format++;
+            *subfmtiter++    = *format++;
+         }
+         else
+            *subfmtiter++    = *format++;
+
+         *subfmtiter++       = '%';
+         *subfmtiter++       = 'n';
+         *subfmtiter++       = '\0';
+
+         if (sizeof(void*) != sizeof(long*))
+            abort(); /* all pointers must have the same size */
+
+         if (asterisk)
+         {
+            int v = sscanf(bufiter, subfmt, &sublen);
+            if (v == EOF)
+               return EOF;
+            if (v != 0)
+               break;
+         }
+         else
+         {
+            int v = sscanf(bufiter, subfmt, va_arg(args, void*), &sublen);
+            if (v == EOF)
+               return EOF;
+            if (v != 1)
+               break;
+         }
+
+         ret++;
+         bufiter += sublen;
+      }
+      else if (isspace((unsigned char)*format))
+      {
+         while (isspace((unsigned char)*bufiter))
+            bufiter++;
+         format++;
+      }
+      else
+      {
+         if (*bufiter != *format)
+            break;
+         bufiter++;
+         format++;
+      }
+   }
+
+   va_end(args);
+   filestream_seek(stream, startpos+(bufiter-buf),
+         RETRO_VFS_SEEK_POSITION_START);
+
+   return ret;
+}
+
+int64_t filestream_seek(RFILE *stream, int64_t offset, int seek_position)
+{
+   int64_t output;
+
+   if (filestream_seek_cb)
+      output = filestream_seek_cb(stream->hfile, offset, seek_position);
+   else
+      output = retro_vfs_file_seek_impl(
+            (libretro_vfs_implementation_file*)stream->hfile,
+            offset, seek_position);
+
+   if (output == VFS_ERROR_RETURN_VALUE)
+      stream->error_flag = true;
+
+   stream->eof_flag      = false;
+
+   return output;
+}
+
+int filestream_eof(RFILE *stream)
+{
+   return stream->eof_flag;
+}
+
+int64_t filestream_tell(RFILE *stream)
+{
+   int64_t output;
+
+   if (filestream_size_cb)
+      output = filestream_tell_cb(stream->hfile);
+   else
+      output = retro_vfs_file_tell_impl(
+            (libretro_vfs_implementation_file*)stream->hfile);
+
+   if (output == VFS_ERROR_RETURN_VALUE)
+      stream->error_flag = true;
+
+   return output;
+}
+
+void filestream_rewind(RFILE *stream)
+{
+   if (!stream)
+      return;
+   filestream_seek(stream, 0L, RETRO_VFS_SEEK_POSITION_START);
+   stream->error_flag = false;
+   stream->eof_flag   = false;
+}
+
+int64_t filestream_read(RFILE *stream, void *s, int64_t len)
+{
+   int64_t output;
+
+   if (filestream_read_cb)
+      output = filestream_read_cb(stream->hfile, s, len);
+   else
+      output = retro_vfs_file_read_impl(
+            (libretro_vfs_implementation_file*)stream->hfile, s, len);
+
+   if (output == VFS_ERROR_RETURN_VALUE)
+      stream->error_flag = true;
+   if (output < len)
+      stream->eof_flag   = true;
+
+   return output;
+}
+
+int filestream_flush(RFILE *stream)
+{
+   int output;
+
+   if (filestream_flush_cb)
+      output = filestream_flush_cb(stream->hfile);
+   else
+      output = retro_vfs_file_flush_impl(
+            (libretro_vfs_implementation_file*)stream->hfile);
+
+   if (output == VFS_ERROR_RETURN_VALUE)
+      stream->error_flag = true;
+
+   return output;
+}
+
+int filestream_delete(const char *path)
+{
+   if (filestream_remove_cb)
+      return filestream_remove_cb(path);
+
+   return retro_vfs_file_remove_impl(path);
+}
+
+int filestream_rename(const char *old_path, const char *new_path)
+{
+   if (filestream_rename_cb)
+      return filestream_rename_cb(old_path, new_path);
+
+   return retro_vfs_file_rename_impl(old_path, new_path);
+}
+
+const char* filestream_get_path(RFILE *stream)
+{
+   if (filestream_get_path_cb)
+      return filestream_get_path_cb(stream->hfile);
+
+   return retro_vfs_file_get_path_impl(
+         (libretro_vfs_implementation_file*)stream->hfile);
+}
+
+int64_t filestream_write(RFILE *stream, const void *s, int64_t len)
+{
+   int64_t output;
+
+   if (filestream_write_cb)
+      output = filestream_write_cb(stream->hfile, s, len);
+   else
+      output = retro_vfs_file_write_impl(
+            (libretro_vfs_implementation_file*)stream->hfile, s, len);
+
+   if (output == VFS_ERROR_RETURN_VALUE)
+      stream->error_flag = true;
+
+   return output;
+}
+
+int filestream_putc(RFILE *stream, int c)
+{
+   char c_char = (char)c;
+   if (!stream)
+      return EOF;
+   return filestream_write(stream, &c_char, 1) == 1 
+      ? (int)(unsigned char)c 
+      : EOF;
+}
+
+int filestream_vprintf(RFILE *stream, const char* format, va_list args)
+{
+   static char buffer[8 * 1024];
+   int64_t num_chars = vsnprintf(buffer, sizeof(buffer),
+         format, args);
+
+   if (num_chars < 0)
+      return -1;
+   else if (num_chars == 0)
+      return 0;
+
+   return (int)filestream_write(stream, buffer, num_chars);
+}
+
+int filestream_printf(RFILE *stream, const char* format, ...)
+{
+   va_list vl;
+   int result;
+   va_start(vl, format);
+   result = filestream_vprintf(stream, format, vl);
+   va_end(vl);
+   return result;
+}
+
+int filestream_error(RFILE *stream)
+{
+   if (stream && stream->error_flag)
+      return 1;
+   return 0;
+}
+
+int filestream_close(RFILE *stream)
+{
+   int output;
+   struct retro_vfs_file_handle* fp = stream->hfile;
+
+   if (filestream_close_cb)
+      output = filestream_close_cb(fp);
+   else
+      output = retro_vfs_file_close_impl(
+            (libretro_vfs_implementation_file*)fp);
+
+   if (output == 0)
+      free(stream);
+
+   return output;
+}
+
+/**
+ * filestream_read_file:
+ * @path             : path to file.
+ * @buf              : buffer to allocate and read the contents of the
+ *                     file into. Needs to be freed manually.
+ *
+ * Read the contents of a file into @buf.
+ *
+ * Returns: number of items read, -1 on error.
+ */
+int64_t filestream_read_file(const char *path, void **buf, int64_t *len)
+{
+   int64_t ret              = 0;
+   int64_t content_buf_size = 0;
+   void *content_buf        = NULL;
+   RFILE *file              = filestream_open(path,
+         RETRO_VFS_FILE_ACCESS_READ,
+         RETRO_VFS_FILE_ACCESS_HINT_NONE);
+
+   if (!file)
+   {
+      *buf = NULL;
+      return 0;
+   }
+
+   content_buf_size = filestream_get_size(file);
+
+   if (content_buf_size < 0)
+      goto error;
+
+   content_buf      = malloc((size_t)(content_buf_size + 1));
+
+   if (!content_buf)
+      goto error;
+   if ((int64_t)(uint64_t)(content_buf_size + 1) != (content_buf_size + 1))
+      goto error;
+
+   ret = filestream_read(file, content_buf, (int64_t)content_buf_size);
+   if (ret < 0)
+      goto error;
+
+   if (filestream_close(file) != 0)
+      if (file)
+         free(file);
+
+   *buf    = content_buf;
+
+   /* Allow for easy reading of strings to be safe.
+    * Will only work with sane character formatting (Unix). */
+   ((char*)content_buf)[ret] = '\0';
+
+   if (len)
+      *len = ret;
+
+   return 1;
+
+error:
+   if (file)
+      if (filestream_close(file) != 0)
+         free(file);
+   if (content_buf)
+      free(content_buf);
+   if (len)
+      *len = -1;
+   *buf = NULL;
+   return 0;
+}
+
+/**
+ * filestream_write_file:
+ * @path             : path to file.
+ * @data             : contents to write to the file.
+ * @size             : size of the contents.
+ *
+ * Writes data to a file.
+ *
+ * Returns: true (1) on success, false (0) otherwise.
+ */
+bool filestream_write_file(const char *path, const void *data, int64_t size)
+{
+   int64_t ret   = 0;
+   RFILE *file   = filestream_open(path,
+         RETRO_VFS_FILE_ACCESS_WRITE,
+         RETRO_VFS_FILE_ACCESS_HINT_NONE);
+   if (!file)
+      return false;
+
+   ret = filestream_write(file, data, size);
+   if (filestream_close(file) != 0)
+      if (file)
+         free(file);
+
+   if (ret != size)
+      return false;
+
+   return true;
+}
+
+/* Returned pointer must be freed by the caller. */
+char* filestream_getline(RFILE *stream)
+{
+   char *newline_tmp  = NULL;
+   size_t cur_size    = 8;
+   size_t idx         = 0;
+   int in             = 0;
+   char *newline      = (char*)malloc(9);
+
+   if (!stream || !newline)
+   {
+      if (newline)
+         free(newline);
+      return NULL;
+   }
+
+   in                 = filestream_getc(stream);
+
+   while (in != EOF && in != '\n')
+   {
+      if (idx == cur_size)
+      {
+         cur_size    *= 2;
+         newline_tmp  = (char*)realloc(newline, cur_size + 1);
+
+         if (!newline_tmp)
+         {
+            free(newline);
+            return NULL;
+         }
+
+         newline     = newline_tmp;
+      }
+
+      newline[idx++] = in;
+      in             = filestream_getc(stream);
+   }
+
+   newline[idx]      = '\0';
+   return newline;
+}
+
+libretro_vfs_implementation_file* filestream_get_vfs_handle(RFILE *stream)
+{
+   return (libretro_vfs_implementation_file*)stream->hfile;
+}
diff --git a/libretro-common/streams/file_stream_transforms.c b/libretro-common/streams/file_stream_transforms.c
new file mode 100644 (file)
index 0000000..099b27b
--- /dev/null
@@ -0,0 +1,159 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+*
+* ---------------------------------------------------------------------------------------
+* The following license statement only applies to this file (file_stream_transforms.c).
+* ---------------------------------------------------------------------------------------
+*
+* Permission is hereby granted, free of charge,
+* to any person obtaining a copy of this software and associated documentation files (the "Software"),
+* to deal in the Software without restriction, including without limitation the rights to
+* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <string.h>
+#include <stdarg.h>
+
+#include <libretro.h>
+#include <streams/file_stream.h>
+
+RFILE* rfopen(const char *path, const char *mode)
+{
+   RFILE          *output  = NULL;
+   unsigned int retro_mode = RETRO_VFS_FILE_ACCESS_READ;
+   bool position_to_end    = false;
+
+   if (strstr(mode, "r"))
+   {
+      retro_mode = RETRO_VFS_FILE_ACCESS_READ;
+      if (strstr(mode, "+"))
+      {
+         retro_mode = RETRO_VFS_FILE_ACCESS_READ_WRITE |
+            RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING;
+      }
+   }
+   else if (strstr(mode, "w"))
+   {
+      retro_mode = RETRO_VFS_FILE_ACCESS_WRITE;
+      if (strstr(mode, "+"))
+         retro_mode = RETRO_VFS_FILE_ACCESS_READ_WRITE;
+   }
+   else if (strstr(mode, "a"))
+   {
+      retro_mode = RETRO_VFS_FILE_ACCESS_WRITE |
+         RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING;
+      position_to_end = true;
+      if (strstr(mode, "+"))
+      {
+         retro_mode = RETRO_VFS_FILE_ACCESS_READ_WRITE |
+            RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING;
+      }
+   }
+
+   output = filestream_open(path, retro_mode,
+         RETRO_VFS_FILE_ACCESS_HINT_NONE);
+   if (output && position_to_end)
+      filestream_seek(output, 0, RETRO_VFS_SEEK_POSITION_END);
+
+   return output;
+}
+
+int rfclose(RFILE* stream)
+{
+   return filestream_close(stream);
+}
+
+int64_t rftell(RFILE* stream)
+{
+   return filestream_tell(stream);
+}
+
+int64_t rfseek(RFILE* stream, int64_t offset, int origin)
+{
+   int seek_position = -1;
+   switch (origin)
+   {
+      case SEEK_SET:
+         seek_position = RETRO_VFS_SEEK_POSITION_START;
+         break;
+      case SEEK_CUR:
+         seek_position = RETRO_VFS_SEEK_POSITION_CURRENT;
+         break;
+      case SEEK_END:
+         seek_position = RETRO_VFS_SEEK_POSITION_END;
+         break;
+   }
+
+   return filestream_seek(stream, offset, seek_position);
+}
+
+int64_t rfread(void* buffer,
+   size_t elem_size, size_t elem_count, RFILE* stream)
+{
+   return (filestream_read(stream, buffer, elem_size * elem_count) / elem_size);
+}
+
+char *rfgets(char *buffer, int maxCount, RFILE* stream)
+{
+   return filestream_gets(stream, buffer, maxCount);
+}
+
+int rfgetc(RFILE* stream)
+{
+   return filestream_getc(stream);
+}
+
+int64_t rfwrite(void const* buffer,
+   size_t elem_size, size_t elem_count, RFILE* stream)
+{
+   return filestream_write(stream, buffer, elem_size * elem_count);
+}
+
+int rfputc(int character, RFILE * stream)
+{
+    return filestream_putc(stream, character);
+}
+
+int64_t rfflush(RFILE * stream)
+{
+    return filestream_flush(stream);
+}
+
+int rfprintf(RFILE * stream, const char * format, ...)
+{
+   int result;
+   va_list vl;
+   va_start(vl, format);
+   result = filestream_vprintf(stream, format, vl);
+   va_end(vl);
+   return result;
+}
+
+int rferror(RFILE* stream)
+{
+   return filestream_error(stream);
+}
+
+int rfeof(RFILE* stream)
+{
+   return filestream_eof(stream);
+}
+
+int rfscanf(RFILE * stream, const char * format, ...)
+{
+   int result;
+   va_list vl;
+   va_start(vl, format);
+   result = filestream_scanf(stream, format, vl);
+   va_end(vl);
+   return result;
+}
diff --git a/libretro-common/string/stdstring.c b/libretro-common/string/stdstring.c
new file mode 100644 (file)
index 0000000..50c35dd
--- /dev/null
@@ -0,0 +1,416 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (stdstring.c).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdint.h>
+#include <ctype.h>
+
+#include <string/stdstring.h>
+#include <encodings/utf.h>
+
+const uint8_t lr_char_props[256] = {
+       /*x0   x1   x2   x3   x4   x5   x6   x7   x8   x9   xA   xB   xC   xD   xE   xF */
+       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x80,0x00,0x00, /* 0x                  */
+       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 1x                  */
+       0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 2x  !"#$%&'()*+,-./ */
+       0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x00,0x00,0x00,0x00,0x00,0x00, /* 3x 0123456789:;<=>? */
+       0x00,0x23,0x23,0x23,0x23,0x23,0x23,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22, /* 4x @ABCDEFGHIJKLMNO */
+       0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x00,0x00,0x00,0x00,0x08, /* 5x PQRSTUVWXYZ[\]^_ */
+       0x00,0x25,0x25,0x25,0x25,0x25,0x25,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24, /* 6x `abcdefghijklmno */
+       0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x00,0x00,0x00,0x00,0x00, /* 7x pqrstuvwxyz{|}~  */
+       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 8x                  */
+       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 9x                  */
+       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Ax                  */
+       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Bx                  */
+       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Cx                  */
+       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Dx                  */
+       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Ex                  */
+       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Fx                  */
+};
+
+char *string_init(const char *src)
+{
+   return src ? strdup(src) : NULL;
+}
+
+void string_set(char **string, const char *src)
+{
+   free(*string);
+   *string = string_init(src);
+}
+
+
+char *string_to_upper(char *s)
+{
+   char *cs = (char *)s;
+   for ( ; *cs != '\0'; cs++)
+      *cs = toupper((unsigned char)*cs);
+   return s;
+}
+
+char *string_to_lower(char *s)
+{
+   char *cs = (char *)s;
+   for ( ; *cs != '\0'; cs++)
+      *cs = tolower((unsigned char)*cs);
+   return s;
+}
+
+char *string_ucwords(char *s)
+{
+   char *cs = (char *)s;
+   for ( ; *cs != '\0'; cs++)
+   {
+      if (*cs == ' ')
+         *(cs+1) = toupper((unsigned char)*(cs+1));
+   }
+
+   s[0] = toupper((unsigned char)s[0]);
+   return s;
+}
+
+char *string_replace_substring(const char *in,
+      const char *pattern, const char *replacement)
+{
+   size_t numhits, pattern_len, replacement_len, outlen;
+   const char *inat   = NULL;
+   const char *inprev = NULL;
+   char          *out = NULL;
+   char        *outat = NULL;
+
+   /* if either pattern or replacement is NULL,
+    * duplicate in and let caller handle it. */
+   if (!pattern || !replacement)
+      return strdup(in);
+
+   pattern_len     = strlen(pattern);
+   replacement_len = strlen(replacement);
+   numhits         = 0;
+   inat            = in;
+
+   while ((inat = strstr(inat, pattern)))
+   {
+      inat += pattern_len;
+      numhits++;
+   }
+
+   outlen          = strlen(in) - pattern_len*numhits + replacement_len*numhits;
+   out             = (char *)malloc(outlen+1);
+
+   if (!out)
+      return NULL;
+
+   outat           = out;
+   inat            = in;
+   inprev          = in;
+
+   while ((inat = strstr(inat, pattern)))
+   {
+      memcpy(outat, inprev, inat-inprev);
+      outat += inat-inprev;
+      memcpy(outat, replacement, replacement_len);
+      outat += replacement_len;
+      inat += pattern_len;
+      inprev = inat;
+   }
+   strcpy(outat, inprev);
+
+   return out;
+}
+
+/* Remove leading whitespaces */
+char *string_trim_whitespace_left(char *const s)
+{
+   if (s && *s)
+   {
+      size_t len     = strlen(s);
+      char *current  = s;
+
+      while (*current && ISSPACE((unsigned char)*current))
+      {
+         ++current;
+         --len;
+      }
+
+      if (s != current)
+         memmove(s, current, len + 1);
+   }
+
+   return s;
+}
+
+/* Remove trailing whitespaces */
+char *string_trim_whitespace_right(char *const s)
+{
+   if (s && *s)
+   {
+      size_t len     = strlen(s);
+      char  *current = s + len - 1;
+
+      while (current != s && ISSPACE((unsigned char)*current))
+      {
+         --current;
+         --len;
+      }
+
+      current[ISSPACE((unsigned char)*current) ? 0 : 1] = '\0';
+   }
+
+   return s;
+}
+
+/* Remove leading and trailing whitespaces */
+char *string_trim_whitespace(char *const s)
+{
+   string_trim_whitespace_right(s);  /* order matters */
+   string_trim_whitespace_left(s);
+
+   return s;
+}
+
+char *word_wrap(char* buffer, const char *string, int line_width, bool unicode, unsigned max_lines)
+{
+   unsigned i     = 0;
+   unsigned len   = (unsigned)strlen(string);
+   unsigned lines = 1;
+
+   while (i < len)
+   {
+      unsigned counter;
+      int pos = (int)(&buffer[i] - buffer);
+
+      /* copy string until the end of the line is reached */
+      for (counter = 1; counter <= (unsigned)line_width; counter++)
+      {
+         const char *character;
+         unsigned char_len;
+         unsigned j = i;
+
+         /* check if end of string reached */
+         if (i == len)
+         {
+            buffer[i] = 0;
+            return buffer;
+         }
+
+         character = utf8skip(&string[i], 1);
+         char_len  = (unsigned)(character - &string[i]);
+
+         if (!unicode)
+            counter += char_len - 1;
+
+         do
+         {
+            buffer[i] = string[i];
+            char_len--;
+            i++;
+         } while (char_len);
+
+         /* check for newlines embedded in the original input
+          * and reset the index */
+         if (buffer[j] == '\n')
+         {
+            lines++;
+            counter = 1;
+         }
+      }
+
+      /* check for whitespace */
+      if (string[i] == ' ')
+      {
+         if ((max_lines == 0 || lines < max_lines))
+         {
+            buffer[i] = '\n';
+            i++;
+            lines++;
+         }
+      }
+      else
+      {
+         int k;
+
+         /* check for nearest whitespace back in string */
+         for (k = i; k > 0; k--)
+         {
+            if (string[k] != ' ' || (max_lines != 0 && lines >= max_lines))
+               continue;
+
+            buffer[k] = '\n';
+            /* set string index back to character after this one */
+            i         = k + 1;
+            lines++;
+            break;
+         }
+
+         if (&buffer[i] - buffer == pos)
+            return buffer;
+      }
+   }
+
+   buffer[i] = 0;
+
+   return buffer;
+}
+
+/* Splits string into tokens seperated by 'delim'
+ * > Returned token string must be free()'d
+ * > Returns NULL if token is not found
+ * > After each call, 'str' is set to the position after the
+ *   last found token
+ * > Tokens *include* empty strings
+ * Usage example:
+ *    char *str      = "1,2,3,4,5,6,7,,,10,";
+ *    char **str_ptr = &str;
+ *    char *token    = NULL;
+ *    while ((token = string_tokenize(str_ptr, ",")))
+ *    {
+ *        printf("%s\n", token);
+ *        free(token);
+ *        token = NULL;
+ *    }
+ */
+char* string_tokenize(char **str, const char *delim)
+{
+   /* Taken from https://codereview.stackexchange.com/questions/216956/strtok-function-thread-safe-supports-empty-tokens-doesnt-change-string# */
+   char *str_ptr    = NULL;
+   char *delim_ptr  = NULL;
+   char *token      = NULL;
+   size_t token_len = 0;
+
+   /* Sanity checks */
+   if (!str || string_is_empty(delim))
+      return NULL;
+
+   str_ptr = *str;
+
+   /* Note: we don't check string_is_empty() here,
+    * empty strings are valid */
+   if (!str_ptr)
+      return NULL;
+
+   /* Search for delimiter */
+   delim_ptr = strstr(str_ptr, delim);
+
+   if (delim_ptr)
+      token_len = delim_ptr - str_ptr;
+   else
+      token_len = strlen(str_ptr);
+
+   /* Allocate token string */
+   token = (char *)malloc((token_len + 1) * sizeof(char));
+
+   if (!token)
+      return NULL;
+
+   /* Copy token */
+   strlcpy(token, str_ptr, (token_len + 1) * sizeof(char));
+   token[token_len] = '\0';
+
+   /* Update input string pointer */
+   *str = delim_ptr ? delim_ptr + strlen(delim) : NULL;
+
+   return token;
+}
+
+/* Removes every instance of character 'c' from 'str' */
+void string_remove_all_chars(char *str, char c)
+{
+   char *read_ptr  = NULL;
+   char *write_ptr = NULL;
+
+   if (string_is_empty(str))
+      return;
+
+   read_ptr  = str;
+   write_ptr = str;
+
+   while (*read_ptr != '\0')
+   {
+      *write_ptr = *read_ptr++;
+      write_ptr += (*write_ptr != c) ? 1 : 0;
+   }
+
+   *write_ptr = '\0';
+}
+
+/* Replaces every instance of character 'find' in 'str'
+ * with character 'replace' */
+void string_replace_all_chars(char *str, char find, char replace)
+{
+   char *str_ptr = str;
+
+   if (string_is_empty(str))
+      return;
+
+   while ((str_ptr = strchr(str_ptr, find)))
+      *str_ptr++ = replace;
+}
+
+/* Converts string to unsigned integer.
+ * Returns 0 if string is invalid  */
+unsigned string_to_unsigned(const char *str)
+{
+   const char *ptr = NULL;
+
+   if (string_is_empty(str))
+      return 0;
+
+   for (ptr = str; *ptr != '\0'; ptr++)
+   {
+      if (!ISDIGIT((unsigned char)*ptr))
+         return 0;
+   }
+
+   return (unsigned)strtoul(str, NULL, 10);
+}
+
+/* Converts hexadecimal string to unsigned integer.
+ * Handles optional leading '0x'.
+ * Returns 0 if string is invalid  */
+unsigned string_hex_to_unsigned(const char *str)
+{
+   const char *hex_str = str;
+   const char *ptr     = NULL;
+   size_t len;
+
+   if (string_is_empty(str))
+      return 0;
+
+   /* Remove leading '0x', if required */
+   len = strlen(str);
+
+   if (len >= 2)
+      if ((str[0] == '0') &&
+          ((str[1] == 'x') || (str[1] == 'X')))
+         hex_str = str + 2;
+
+   if (string_is_empty(hex_str))
+      return 0;
+
+   /* Check for valid characters */
+   for (ptr = hex_str; *ptr != '\0'; ptr++)
+   {
+      if (!isxdigit((unsigned char)*ptr))
+         return 0;
+   }
+
+   return (unsigned)strtoul(hex_str, NULL, 16);
+}
diff --git a/libretro-common/time/rtime.c b/libretro-common/time/rtime.c
new file mode 100644 (file)
index 0000000..d66c228
--- /dev/null
@@ -0,0 +1,81 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (rtime.c).
+ * ---------------------------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifdef HAVE_THREADS
+#include <rthreads/rthreads.h>
+#include <retro_assert.h>
+#include <stdlib.h>
+#endif
+
+#include <string.h>
+#include <time/rtime.h>
+
+#ifdef HAVE_THREADS
+/* TODO/FIXME - global */
+slock_t *rtime_localtime_lock = NULL;
+#endif
+
+/* Must be called before using rtime_localtime() */
+void rtime_init(void)
+{
+   rtime_deinit();
+#ifdef HAVE_THREADS
+   if (!rtime_localtime_lock)
+      rtime_localtime_lock = slock_new();
+
+   retro_assert(rtime_localtime_lock);
+#endif
+}
+
+/* Must be called upon program termination */
+void rtime_deinit(void)
+{
+#ifdef HAVE_THREADS
+   if (rtime_localtime_lock)
+   {
+      slock_free(rtime_localtime_lock);
+      rtime_localtime_lock = NULL;
+   }
+#endif
+}
+
+/* Thread-safe wrapper for localtime() */
+struct tm *rtime_localtime(const time_t *timep, struct tm *result)
+{
+   struct tm *time_info = NULL;
+
+   /* Lock mutex */
+#ifdef HAVE_THREADS
+   slock_lock(rtime_localtime_lock);
+#endif
+
+   time_info = localtime(timep);
+   if (time_info)
+      memcpy(result, time_info, sizeof(struct tm));
+
+   /* Unlock mutex */
+#ifdef HAVE_THREADS
+   slock_unlock(rtime_localtime_lock);
+#endif
+
+   return result;
+}
diff --git a/libretro-common/vfs/vfs_implementation.c b/libretro-common/vfs/vfs_implementation.c
new file mode 100644 (file)
index 0000000..b710e2f
--- /dev/null
@@ -0,0 +1,1329 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+*
+* ---------------------------------------------------------------------------------------
+* The following license statement only applies to this file (vfs_implementation.c).
+* ---------------------------------------------------------------------------------------
+*
+* Permission is hereby granted, free of charge,
+* to any person obtaining a copy of this software and associated documentation files (the "Software"),
+* to deal in the Software without restriction, including without limitation the rights to
+* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#include <string/stdstring.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if defined(_WIN32)
+#  ifdef _MSC_VER
+#    define setmode _setmode
+#  endif
+#include <sys/stat.h>
+#  ifdef _XBOX
+#    include <xtl.h>
+#    define INVALID_FILE_ATTRIBUTES -1
+#  else
+
+#    include <fcntl.h>
+#    include <direct.h>
+#    include <windows.h>
+#  endif
+#    include <io.h>
+#else
+#  if defined(PSP)
+#    include <pspiofilemgr.h>
+#  endif
+#  include <sys/types.h>
+#  include <sys/stat.h>
+#  if !defined(VITA)
+#  include <dirent.h>
+#  endif
+#  include <unistd.h>
+#  if defined(ORBIS)
+#  include <sys/fcntl.h>
+#  include <sys/dirent.h>
+#  include <orbisFile.h>
+#  endif
+#endif
+
+#if defined (__CELLOS_LV2__)  && !defined(__PSL1GHT__)
+#include <cell/cell_fs.h>
+#define O_RDONLY CELL_FS_O_RDONLY
+#define O_WRONLY CELL_FS_O_WRONLY
+#define O_CREAT CELL_FS_O_CREAT
+#define O_TRUNC CELL_FS_O_TRUNC
+#define O_RDWR CELL_FS_O_RDWR
+#else
+#include <fcntl.h>
+#endif
+
+/* TODO: Some things are duplicated but I'm really afraid of breaking other platforms by touching this */
+#if defined(VITA)
+#  include <psp2/io/fcntl.h>
+#  include <psp2/io/dirent.h>
+#  include <psp2/io/stat.h>
+#elif defined(ORBIS)
+#  include <orbisFile.h>
+#  include <ps4link.h>
+#  include <sys/dirent.h>
+#  include <sys/fcntl.h>
+#elif !defined(_WIN32)
+#  if defined(PSP)
+#    include <pspiofilemgr.h>
+#  endif
+#  include <sys/types.h>
+#  include <sys/stat.h>
+#  include <dirent.h>
+#  include <unistd.h>
+#endif
+
+#if (defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)) || defined(__QNX__) || defined(PSP)
+#include <unistd.h> /* stat() is defined here */
+#endif
+
+#ifdef __APPLE__
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+#ifdef __HAIKU__
+#include <kernel/image.h>
+#endif
+#ifndef __MACH__
+#include <compat/strl.h>
+#include <compat/posix_string.h>
+#endif
+#include <compat/strcasestr.h>
+#include <retro_miscellaneous.h>
+#include <encodings/utf.h>
+
+#if defined(_WIN32)
+#ifndef _XBOX
+#if defined(_MSC_VER) && _MSC_VER <= 1200
+#define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
+#endif
+#endif
+#elif defined(VITA)
+#define SCE_ERROR_ERRNO_EEXIST 0x80010011
+#include <psp2/io/fcntl.h>
+#include <psp2/io/dirent.h>
+#include <psp2/io/stat.h>
+#else
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+#if defined(ORBIS)
+#include <orbisFile.h>
+#include <sys/fcntl.h>
+#include <sys/dirent.h>
+#endif
+#if defined(PSP)
+#include <pspkernel.h>
+#endif
+
+#if defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
+#include <cell/cell_fs.h>
+#endif
+
+#if defined(VITA)
+#define FIO_S_ISDIR SCE_S_ISDIR
+#endif
+
+#if (defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)) || defined(__QNX__) || defined(PSP)
+#include <unistd.h> /* stat() is defined here */
+#endif
+
+/* Assume W-functions do not work below Win2K and Xbox platforms */
+#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 || defined(_XBOX)
+
+#ifndef LEGACY_WIN32
+#define LEGACY_WIN32
+#endif
+
+#endif
+
+#if defined(_WIN32)
+#if defined(_MSC_VER) && _MSC_VER >= 1400
+#define ATLEAST_VC2005
+#endif
+#endif
+
+#include <vfs/vfs_implementation.h>
+#include <libretro.h>
+#include <memmap.h>
+#include <encodings/utf.h>
+#include <compat/fopen_utf8.h>
+#include <file/file_path.h>
+
+#ifdef HAVE_CDROM
+#include <vfs/vfs_implementation_cdrom.h>
+#endif
+
+#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)
+#ifndef HAVE_64BIT_OFFSETS
+#define HAVE_64BIT_OFFSETS
+#endif
+#endif
+
+#define RFILE_HINT_UNBUFFERED (1 << 8)
+
+int64_t retro_vfs_file_seek_internal(
+      libretro_vfs_implementation_file *stream,
+      int64_t offset, int whence)
+{
+   if (!stream)
+      return -1;
+
+   if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
+   {
+#ifdef HAVE_CDROM
+      if (stream->scheme == VFS_SCHEME_CDROM)
+         return retro_vfs_file_seek_cdrom(stream, offset, whence);
+#endif
+#ifdef ATLEAST_VC2005
+      /* VC2005 and up have a special 64-bit fseek */
+      return _fseeki64(stream->fp, offset, whence);
+#elif defined(ORBIS)
+      {
+         int ret = orbisLseek(stream->fd, offset, whence);
+         if (ret < 0)
+            return -1;
+         return 0;
+      }
+#elif defined(HAVE_64BIT_OFFSETS)
+      return fseeko(stream->fp, (off_t)offset, whence);
+#else
+      return fseek(stream->fp, (long)offset, whence);
+#endif
+   }
+#ifdef HAVE_MMAP
+   /* Need to check stream->mapped because this function is
+    * called in filestream_open() */
+   if (stream->mapped && stream->hints &
+         RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
+   {
+      /* fseek() returns error on under/overflow but
+       * allows cursor > EOF for
+       read-only file descriptors. */
+      switch (whence)
+      {
+         case SEEK_SET:
+            if (offset < 0)
+               return -1;
+
+            stream->mappos = offset;
+            break;
+
+         case SEEK_CUR:
+            if (  (offset < 0 && stream->mappos + offset > stream->mappos) ||
+                  (offset > 0 && stream->mappos + offset < stream->mappos))
+               return -1;
+
+            stream->mappos += offset;
+            break;
+
+         case SEEK_END:
+            if (stream->mapsize + offset < stream->mapsize)
+               return -1;
+
+            stream->mappos = stream->mapsize + offset;
+            break;
+      }
+      return stream->mappos;
+   }
+#endif
+
+   if (lseek(stream->fd, (off_t)offset, whence) < 0)
+      return -1;
+
+   return 0;
+}
+
+/**
+ * retro_vfs_file_open_impl:
+ * @path               : path to file
+ * @mode               : file mode to use when opening (read/write)
+ * @hints              :
+ *
+ * Opens a file for reading or writing, depending on the requested mode.
+ * Returns a pointer to an RFILE if opened successfully, otherwise NULL.
+ **/
+
+libretro_vfs_implementation_file *retro_vfs_file_open_impl(
+      const char *path, unsigned mode, unsigned hints)
+{
+#if defined(VFS_FRONTEND) || defined(HAVE_CDROM)
+   int                             path_len = (int)strlen(path);
+#endif
+#ifdef VFS_FRONTEND
+   const char                 *dumb_prefix  = "vfsonly://";
+   size_t                   dumb_prefix_siz = STRLEN_CONST("vfsonly://");
+   int                      dumb_prefix_len = (int)dumb_prefix_siz;
+#endif
+#ifdef HAVE_CDROM
+   const char *cdrom_prefix                 = "cdrom://";
+   size_t cdrom_prefix_siz                  = STRLEN_CONST("cdrom://");
+   int cdrom_prefix_len                     = (int)cdrom_prefix_siz;
+#endif
+   int                                flags = 0;
+   const char                     *mode_str = NULL;
+   libretro_vfs_implementation_file *stream = 
+      (libretro_vfs_implementation_file*)
+      malloc(sizeof(*stream));
+
+   if (!stream)
+      return NULL;
+
+   stream->fd                     = 0;
+   stream->hints                  = hints;
+   stream->size                   = 0;
+   stream->buf                    = NULL;
+   stream->fp                     = NULL;
+#ifdef _WIN32
+   stream->fh                     = 0;
+#endif
+   stream->orig_path              = NULL;
+   stream->mappos                 = 0;
+   stream->mapsize                = 0;
+   stream->mapped                 = NULL;
+   stream->scheme                 = VFS_SCHEME_NONE;
+
+#ifdef VFS_FRONTEND
+   if (path_len >= dumb_prefix_len)
+      if (!memcmp(path, dumb_prefix, dumb_prefix_len))
+         path             += dumb_prefix_siz;
+#endif
+
+#ifdef HAVE_CDROM
+   stream->cdrom.cue_buf          = NULL;
+   stream->cdrom.cue_len          = 0;
+   stream->cdrom.byte_pos         = 0;
+   stream->cdrom.drive            = 0;
+   stream->cdrom.cur_min          = 0;
+   stream->cdrom.cur_sec          = 0;
+   stream->cdrom.cur_frame        = 0;
+   stream->cdrom.cur_track        = 0;
+   stream->cdrom.cur_lba          = 0;
+   stream->cdrom.last_frame_lba   = 0;
+   stream->cdrom.last_frame[0]    = '\0';
+   stream->cdrom.last_frame_valid = false;
+
+   if (path_len > cdrom_prefix_len)
+   {
+      if (!memcmp(path, cdrom_prefix, cdrom_prefix_len))
+      {
+         path             += cdrom_prefix_siz;
+         stream->scheme    = VFS_SCHEME_CDROM;
+      }
+   }
+#endif
+
+   stream->orig_path       = strdup(path);
+
+#ifdef HAVE_MMAP
+   if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS && mode == RETRO_VFS_FILE_ACCESS_READ)
+      stream->hints |= RFILE_HINT_UNBUFFERED;
+   else
+#endif
+      stream->hints &= ~RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS;
+
+   switch (mode)
+   {
+      case RETRO_VFS_FILE_ACCESS_READ:
+         mode_str = "rb";
+
+         flags    = O_RDONLY;
+#ifdef _WIN32
+         flags   |= O_BINARY;
+#endif
+         break;
+
+      case RETRO_VFS_FILE_ACCESS_WRITE:
+         mode_str = "wb";
+
+         flags    = O_WRONLY | O_CREAT | O_TRUNC;
+#if !defined(ORBIS)
+#if !defined(_WIN32)
+         flags   |= S_IRUSR | S_IWUSR;
+#else
+         flags   |= O_BINARY;
+#endif
+#endif
+         break;
+
+      case RETRO_VFS_FILE_ACCESS_READ_WRITE:
+         mode_str = "w+b";
+         flags    = O_RDWR | O_CREAT | O_TRUNC;
+#if !defined(ORBIS)
+#if !defined(_WIN32)
+         flags   |= S_IRUSR | S_IWUSR;
+#else
+         flags   |= O_BINARY;
+#endif
+#endif
+         break;
+
+      case RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING:
+      case RETRO_VFS_FILE_ACCESS_READ_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING:
+         mode_str = "r+b";
+
+         flags    = O_RDWR;
+#if !defined(ORBIS)
+#if !defined(_WIN32)
+         flags   |= S_IRUSR | S_IWUSR;
+#else
+         flags   |= O_BINARY;
+#endif
+#endif
+         break;
+
+      default:
+         goto error;
+   }
+
+   if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
+   {
+#ifdef ORBIS
+      int fd = orbisOpen(path, flags, 0644);
+      if (fd < 0)
+      {
+         stream->fd = -1;
+         goto error;
+      }
+      stream->fd    = fd;
+#else
+      FILE *fp;
+#ifdef HAVE_CDROM
+      if (stream->scheme == VFS_SCHEME_CDROM)
+      {
+         retro_vfs_file_open_cdrom(stream, path, mode, hints);
+#if defined(_WIN32) && !defined(_XBOX)
+         if (!stream->fh)
+            goto error;
+#else
+         if (!stream->fp)
+            goto error;
+#endif
+      }
+      else
+#endif
+      {
+         fp = (FILE*)fopen_utf8(path, mode_str);
+
+         if (!fp)
+            goto error;
+
+         stream->fp  = fp;
+      }
+      /* Regarding setvbuf:
+       *
+       * https://www.freebsd.org/cgi/man.cgi?query=setvbuf&apropos=0&sektion=0&manpath=FreeBSD+11.1-RELEASE&arch=default&format=html
+       *
+       * If the size argument is not zero but buf is NULL, 
+       * a buffer of the given size will be allocated immediately, and
+       * released on close. This is an extension to ANSI C.
+       *
+       * Since C89 does not support specifying a NULL buffer 
+       * with a non-zero size, we create and track our own buffer for it.
+       */
+      /* TODO: this is only useful for a few platforms, 
+       * find which and add ifdef */
+#if defined(_3DS)
+      if (stream->scheme != VFS_SCHEME_CDROM)
+      {
+         stream->buf = (char*)calloc(1, 0x10000);
+         if (stream->fp)
+            setvbuf(stream->fp, stream->buf, _IOFBF, 0x10000);
+      }
+#elif !defined(PSP)
+      if (stream->scheme != VFS_SCHEME_CDROM)
+      {
+         stream->buf = (char*)calloc(1, 0x4000);
+         if (stream->fp)
+            setvbuf(stream->fp, stream->buf, _IOFBF, 0x4000);
+      }
+#endif
+#endif
+   }
+   else
+   {
+#if defined(_WIN32) && !defined(_XBOX)
+#if defined(LEGACY_WIN32)
+      char *path_local    = utf8_to_local_string_alloc(path);
+      stream->fd          = open(path_local, flags, 0);
+      if (path_local)
+         free(path_local);
+#else
+      wchar_t * path_wide = utf8_to_utf16_string_alloc(path);
+      stream->fd          = _wopen(path_wide, flags, 0);
+      if (path_wide)
+         free(path_wide);
+#endif
+#else
+      stream->fd          = open(path, flags, 0);
+#endif
+
+      if (stream->fd == -1)
+         goto error;
+
+#ifdef HAVE_MMAP
+      if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
+      {
+         stream->mappos  = 0;
+         stream->mapped  = NULL;
+         stream->mapsize = retro_vfs_file_seek_internal(stream, 0, SEEK_END);
+
+         if (stream->mapsize == (uint64_t)-1)
+            goto error;
+
+         retro_vfs_file_seek_internal(stream, 0, SEEK_SET);
+
+         stream->mapped = (uint8_t*)mmap((void*)0,
+               stream->mapsize, PROT_READ,  MAP_SHARED, stream->fd, 0);
+
+         if (stream->mapped == MAP_FAILED)
+            stream->hints &= ~RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS;
+      }
+#endif
+   }
+#ifdef ORBIS
+   stream->size = orbisLseek(stream->fd, 0, SEEK_END);
+   orbisLseek(stream->fd, 0, SEEK_SET);
+#else
+#ifdef HAVE_CDROM
+   if (stream->scheme == VFS_SCHEME_CDROM)
+   {
+      retro_vfs_file_seek_cdrom(stream, 0, SEEK_SET);
+      retro_vfs_file_seek_cdrom(stream, 0, SEEK_END);
+
+      stream->size = retro_vfs_file_tell_impl(stream);
+
+      retro_vfs_file_seek_cdrom(stream, 0, SEEK_SET);
+   }
+   else
+#endif
+   {
+      retro_vfs_file_seek_internal(stream, 0, SEEK_SET);
+      retro_vfs_file_seek_internal(stream, 0, SEEK_END);
+
+      stream->size = retro_vfs_file_tell_impl(stream);
+
+      retro_vfs_file_seek_internal(stream, 0, SEEK_SET);
+   }
+#endif
+   return stream;
+
+error:
+   retro_vfs_file_close_impl(stream);
+   return NULL;
+}
+
+int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream)
+{
+   if (!stream)
+      return -1;
+
+#ifdef HAVE_CDROM
+   if (stream->scheme == VFS_SCHEME_CDROM)
+   {
+      retro_vfs_file_close_cdrom(stream);
+      goto end;
+   }
+#endif
+
+   if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
+   {
+      if (stream->fp)
+         fclose(stream->fp);
+   }
+   else
+   {
+#ifdef HAVE_MMAP
+      if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
+         munmap(stream->mapped, stream->mapsize);
+#endif
+   }
+
+   if (stream->fd > 0)
+   {
+#ifdef ORBIS
+      orbisClose(stream->fd);
+      stream->fd = -1;
+#else
+      close(stream->fd);
+#endif
+   }
+#ifdef HAVE_CDROM
+end:
+   if (stream->cdrom.cue_buf)
+      free(stream->cdrom.cue_buf);
+#endif
+   if (stream->buf)
+      free(stream->buf);
+
+   if (stream->orig_path)
+      free(stream->orig_path);
+
+   free(stream);
+
+   return 0;
+}
+
+int retro_vfs_file_error_impl(libretro_vfs_implementation_file *stream)
+{
+#ifdef HAVE_CDROM
+   if (stream->scheme == VFS_SCHEME_CDROM)
+      return retro_vfs_file_error_cdrom(stream);
+#endif
+#ifdef ORBIS
+   /* TODO/FIXME - implement this? */
+   return 0;
+#else
+   return ferror(stream->fp);
+#endif
+}
+
+int64_t retro_vfs_file_size_impl(libretro_vfs_implementation_file *stream)
+{
+   if (stream)
+      return stream->size;
+   return 0;
+}
+
+int64_t retro_vfs_file_truncate_impl(libretro_vfs_implementation_file *stream, int64_t length)
+{
+   if (!stream)
+      return -1;
+
+#ifdef _WIN32
+   if (_chsize(_fileno(stream->fp), length) != 0)
+      return -1;
+#elif !defined(VITA) && !defined(PSP) && !defined(PS2) && !defined(ORBIS) && (!defined(SWITCH) || defined(HAVE_LIBNX))
+   if (ftruncate(fileno(stream->fp), (off_t)length) != 0)
+      return -1;
+#endif
+
+   return 0;
+}
+
+int64_t retro_vfs_file_tell_impl(libretro_vfs_implementation_file *stream)
+{
+   if (!stream)
+      return -1;
+
+   if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
+   {
+#ifdef HAVE_CDROM
+      if (stream->scheme == VFS_SCHEME_CDROM)
+         return retro_vfs_file_tell_cdrom(stream);
+#endif
+#ifdef ORBIS
+      {
+         int64_t ret = orbisLseek(stream->fd, 0, SEEK_CUR);
+         if (ret < 0)
+            return -1;
+         return ret;
+      }
+#else
+#ifdef ATLEAST_VC2005
+      /* VC2005 and up have a special 64-bit ftell */
+      return _ftelli64(stream->fp);
+#elif defined(HAVE_64BIT_OFFSETS)
+      return ftello(stream->fp);
+#else
+      return ftell(stream->fp);
+#endif
+#endif
+   }
+#ifdef HAVE_MMAP
+   /* Need to check stream->mapped because this function
+    * is called in filestream_open() */
+   if (stream->mapped && stream->hints & 
+         RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
+      return stream->mappos;
+#endif
+   if (lseek(stream->fd, 0, SEEK_CUR) < 0)
+      return -1;
+
+   return 0;
+}
+
+int64_t retro_vfs_file_seek_impl(libretro_vfs_implementation_file *stream,
+      int64_t offset, int seek_position)
+{
+   int whence = -1;
+   switch (seek_position)
+   {
+      case RETRO_VFS_SEEK_POSITION_START:
+         whence = SEEK_SET;
+         break;
+      case RETRO_VFS_SEEK_POSITION_CURRENT:
+         whence = SEEK_CUR;
+         break;
+      case RETRO_VFS_SEEK_POSITION_END:
+         whence = SEEK_END;
+         break;
+   }
+
+   return retro_vfs_file_seek_internal(stream, offset, whence);
+}
+
+int64_t retro_vfs_file_read_impl(libretro_vfs_implementation_file *stream,
+      void *s, uint64_t len)
+{
+   if (!stream || !s)
+      return -1;
+
+   if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
+   {
+#ifdef HAVE_CDROM
+      if (stream->scheme == VFS_SCHEME_CDROM)
+         return retro_vfs_file_read_cdrom(stream, s, len);
+#endif
+#ifdef ORBIS
+      if (orbisRead(stream->fd, s, (size_t)len) < 0)
+         return -1;
+      return 0;
+#else
+      return fread(s, 1, (size_t)len, stream->fp);
+#endif
+   }
+#ifdef HAVE_MMAP
+   if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
+   {
+      if (stream->mappos > stream->mapsize)
+         return -1;
+
+      if (stream->mappos + len > stream->mapsize)
+         len = stream->mapsize - stream->mappos;
+
+      memcpy(s, &stream->mapped[stream->mappos], len);
+      stream->mappos += len;
+
+      return len;
+   }
+#endif
+
+   return read(stream->fd, s, (size_t)len);
+}
+
+int64_t retro_vfs_file_write_impl(libretro_vfs_implementation_file *stream, const void *s, uint64_t len)
+{
+   if (!stream)
+      return -1;
+
+   if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
+   {
+#ifdef ORBIS
+      if (orbisWrite(stream->fd, s, (size_t)len) < 0)
+         return -1;
+      return 0;
+#else
+      return fwrite(s, 1, (size_t)len, stream->fp);
+#endif
+   }
+
+#ifdef HAVE_MMAP
+   if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
+      return -1;
+#endif
+   return write(stream->fd, s, (size_t)len);
+}
+
+int retro_vfs_file_flush_impl(libretro_vfs_implementation_file *stream)
+{
+   if (!stream)
+      return -1;
+#ifdef ORBIS
+   return 0;
+#else
+   return fflush(stream->fp) == 0 ? 0 : -1;
+#endif
+}
+
+int retro_vfs_file_remove_impl(const char *path)
+{
+#if defined(_WIN32) && !defined(_XBOX)
+   /* Win32 (no Xbox) */
+
+#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500
+   char *path_local    = NULL;
+#else
+   wchar_t *path_wide  = NULL;
+#endif
+   if (!path || !*path)
+      return -1;
+#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500
+   path_local = utf8_to_local_string_alloc(path);
+
+   if (path_local)
+   {
+      int ret = remove(path_local);
+      free(path_local);
+
+      if (ret == 0)
+         return 0;
+   }
+#else
+   path_wide = utf8_to_utf16_string_alloc(path);
+
+   if (path_wide)
+   {
+      int ret = _wremove(path_wide);
+      free(path_wide);
+
+      if (ret == 0)
+         return 0;
+   }
+#endif
+   return -1;
+#elif defined(ORBIS)
+   /* Orbis
+    * TODO/FIXME - stub for now */
+   return 0;
+#else
+   if (remove(path) == 0)
+      return 0;
+   return -1;
+#endif
+}
+
+int retro_vfs_file_rename_impl(const char *old_path, const char *new_path)
+{
+#if defined(_WIN32) && !defined(_XBOX)
+   /* Win32 (no Xbox) */
+   int ret                 = -1;
+#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500
+   char *old_path_local    = NULL;
+#else
+   wchar_t *old_path_wide  = NULL;
+#endif
+
+   if (!old_path || !*old_path || !new_path || !*new_path)
+      return -1;
+
+#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500
+   old_path_local = utf8_to_local_string_alloc(old_path);
+
+   if (old_path_local)
+   {
+      char *new_path_local = utf8_to_local_string_alloc(new_path);
+
+      if (new_path_local)
+      {
+         if (rename(old_path_local, new_path_local) == 0)
+            ret = 0;
+         free(new_path_local);
+      }
+
+      free(old_path_local);
+   }
+#else
+   old_path_wide = utf8_to_utf16_string_alloc(old_path);
+
+   if (old_path_wide)
+   {
+      wchar_t *new_path_wide = utf8_to_utf16_string_alloc(new_path);
+
+      if (new_path_wide)
+      {
+         if (_wrename(old_path_wide, new_path_wide) == 0)
+            ret = 0;
+         free(new_path_wide);
+      }
+
+      free(old_path_wide);
+   }
+#endif
+   return ret;
+
+#elif defined(ORBIS)
+   /* Orbis */
+   /* TODO/FIXME - Stub for now */
+   if (!old_path || !*old_path || !new_path || !*new_path)
+      return -1;
+   return 0;
+
+#else
+   /* Every other platform */
+   if (!old_path || !*old_path || !new_path || !*new_path)
+      return -1;
+   return rename(old_path, new_path) == 0 ? 0 : -1;
+#endif
+}
+
+const char *retro_vfs_file_get_path_impl(
+      libretro_vfs_implementation_file *stream)
+{
+   /* should never happen, do something noisy so caller can be fixed */
+   if (!stream)
+      abort();
+   return stream->orig_path;
+}
+
+int retro_vfs_stat_impl(const char *path, int32_t *size)
+{
+   bool is_dir               = false;
+   bool is_character_special = false;
+#if defined(VITA) || defined(PSP)
+   /* Vita / PSP */
+   SceIoStat buf;
+   int dir_ret;
+   char *tmp                 = NULL;
+   size_t len                = 0;
+
+   if (!path || !*path)
+      return 0;
+
+   tmp                       = strdup(path);
+   len                       = strlen(tmp);
+   if (tmp[len-1] == '/')
+      tmp[len-1] = '\0';
+
+   dir_ret                   = sceIoGetstat(tmp, &buf);
+   free(tmp);
+   if (dir_ret < 0)
+      return 0;
+
+   if (size)
+      *size                  = (int32_t)buf.st_size;
+
+   is_dir                    = FIO_S_ISDIR(buf.st_mode);
+#elif defined(ORBIS)
+   /* Orbis */
+   int dir_ret               = 0;
+
+   if (!path || !*path)
+      return 0;
+
+   if (size)
+      *size                  = (int32_t)buf.st_size;
+
+   dir_ret                   = orbisDopen(path);
+   is_dir                    = dir_ret > 0;
+   orbisDclose(dir_ret);
+
+   is_character_special      = S_ISCHR(buf.st_mode);
+#elif defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
+   /* CellOS Lv2 */
+   CellFsStat buf;
+
+   if (!path || !*path)
+      return 0;
+   if (cellFsStat(path, &buf) < 0)
+      return 0;
+
+   if (size)
+      *size                  = (int32_t)buf.st_size;
+
+   is_dir                    = ((buf.st_mode & S_IFMT) == S_IFDIR);
+
+#elif defined(_WIN32)
+   /* Windows */
+   DWORD file_info;
+   struct _stat buf;
+#if defined(LEGACY_WIN32)
+   char *path_local          = NULL;
+#else
+   wchar_t *path_wide        = NULL;
+#endif
+
+   if (!path || !*path)
+      return 0;
+
+#if defined(LEGACY_WIN32)
+   path_local                = utf8_to_local_string_alloc(path);
+   file_info                 = GetFileAttributes(path_local);
+
+   if (!string_is_empty(path_local))
+      _stat(path_local, &buf);
+
+   if (path_local)
+      free(path_local);
+#else
+   path_wide                 = utf8_to_utf16_string_alloc(path);
+   file_info                 = GetFileAttributesW(path_wide);
+
+   _wstat(path_wide, &buf);
+
+   if (path_wide)
+      free(path_wide);
+#endif
+
+   if (file_info == INVALID_FILE_ATTRIBUTES)
+      return 0;
+
+   if (size)
+      *size = (int32_t)buf.st_size;
+
+   is_dir = (file_info & FILE_ATTRIBUTE_DIRECTORY);
+
+#elif defined(GEKKO)
+   /* On GEKKO platforms, paths cannot have
+    * trailing slashes - we must therefore
+    * remove them */
+   char *path_buf = NULL;
+   int stat_ret   = -1;
+   struct stat stat_buf;
+   size_t len;
+
+   if (string_is_empty(path))
+      return 0;
+
+   path_buf = strdup(path);
+   if (!path_buf)
+      return 0;
+
+   len = strlen(path_buf);
+   if (len > 0)
+      if (path_buf[len - 1] == '/')
+         path_buf[len - 1] = '\0';
+
+   stat_ret = stat(path_buf, &stat_buf);
+   free(path_buf);
+
+   if (stat_ret < 0)
+      return 0;
+
+   if (size)
+      *size             = (int32_t)stat_buf.st_size;
+
+   is_dir               = S_ISDIR(stat_buf.st_mode);
+   is_character_special = S_ISCHR(stat_buf.st_mode);
+
+#else
+   /* Every other platform */
+   struct stat buf;
+
+   if (!path || !*path)
+      return 0;
+   if (stat(path, &buf) < 0)
+      return 0;
+
+   if (size)
+      *size             = (int32_t)buf.st_size;
+
+   is_dir               = S_ISDIR(buf.st_mode);
+   is_character_special = S_ISCHR(buf.st_mode);
+#endif
+   return RETRO_VFS_STAT_IS_VALID | (is_dir ? RETRO_VFS_STAT_IS_DIRECTORY : 0) | (is_character_special ? RETRO_VFS_STAT_IS_CHARACTER_SPECIAL : 0);
+}
+
+#if defined(VITA)
+#define path_mkdir_error(ret) (((ret) == SCE_ERROR_ERRNO_EEXIST))
+#elif defined(PSP) || defined(PS2) || defined(_3DS) || defined(WIIU) || defined(SWITCH) || defined(ORBIS)
+#define path_mkdir_error(ret) ((ret) == -1)
+#else
+#define path_mkdir_error(ret) ((ret) < 0 && errno == EEXIST)
+#endif
+
+int retro_vfs_mkdir_impl(const char *dir)
+{
+#if defined(_WIN32)
+#ifdef LEGACY_WIN32
+   int ret        = _mkdir(dir);
+#else
+   wchar_t *dir_w = utf8_to_utf16_string_alloc(dir);
+   int       ret  = -1;
+
+   if (dir_w)
+   {
+      ret = _wmkdir(dir_w);
+      free(dir_w);
+   }
+#endif
+#elif defined(IOS)
+   int ret = mkdir(dir, 0755);
+#elif defined(VITA) || defined(PSP)
+   int ret = sceIoMkdir(dir, 0777);
+#elif defined(ORBIS)
+   int ret = orbisMkdir(dir, 0755);
+#elif defined(__QNX__)
+   int ret = mkdir(dir, 0777);
+#elif defined(GEKKO)
+   /* On GEKKO platforms, mkdir() fails if
+    * the path has a trailing slash. We must
+    * therefore remove it. */
+   int ret = -1;
+   if (!string_is_empty(dir))
+   {
+      char *dir_buf = strdup(dir);
+
+      if (dir_buf)
+      {
+         size_t len = strlen(dir_buf);
+
+         if (len > 0)
+            if (dir_buf[len - 1] == '/')
+               dir_buf[len - 1] = '\0';
+
+         ret = mkdir(dir_buf, 0750);
+
+         free(dir_buf);
+      }
+   }
+#else
+   int ret = mkdir(dir, 0750);
+#endif
+
+   if (path_mkdir_error(ret))
+      return -2;
+   return ret < 0 ? -1 : 0;
+}
+
+#ifdef VFS_FRONTEND
+struct retro_vfs_dir_handle
+#else
+struct libretro_vfs_implementation_dir
+#endif
+{
+   char* orig_path;
+#if defined(_WIN32)
+#if defined(LEGACY_WIN32)
+   WIN32_FIND_DATA entry;
+#else
+   WIN32_FIND_DATAW entry;
+#endif
+   HANDLE directory;
+   bool next;
+   char path[PATH_MAX_LENGTH];
+#elif defined(VITA) || defined(PSP)
+   SceUID directory;
+   SceIoDirent entry;
+#elif defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
+   CellFsErrno error;
+   int directory;
+   CellFsDirent entry;
+#elif defined(ORBIS)
+   int directory;
+   struct dirent entry;
+#else
+   DIR *directory;
+   const struct dirent *entry;
+#endif
+};
+
+static bool dirent_check_error(libretro_vfs_implementation_dir *rdir)
+{
+#if defined(_WIN32)
+   return (rdir->directory == INVALID_HANDLE_VALUE);
+#elif defined(VITA) || defined(PSP) || defined(ORBIS)
+   return (rdir->directory < 0);
+#elif defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
+   return (rdir->error != CELL_FS_SUCCEEDED);
+#else
+   return !(rdir->directory);
+#endif
+}
+
+libretro_vfs_implementation_dir *retro_vfs_opendir_impl(
+      const char *name, bool include_hidden)
+{
+#if defined(_WIN32)
+   unsigned path_len;
+   char path_buf[1024];
+   size_t copied      = 0;
+#if defined(LEGACY_WIN32)
+   char *path_local   = NULL;
+#else
+   wchar_t *path_wide = NULL;
+#endif
+#endif
+   libretro_vfs_implementation_dir *rdir;
+
+   /*Reject null or empty string paths*/
+   if (!name || (*name == 0))
+      return NULL;
+
+   /*Allocate RDIR struct. Tidied later with retro_closedir*/
+   rdir = (libretro_vfs_implementation_dir*)calloc(1, sizeof(*rdir));
+   if (!rdir)
+      return NULL;
+
+   rdir->orig_path       = strdup(name);
+
+#if defined(_WIN32)
+   path_buf[0]           = '\0';
+   path_len              = strlen(name);
+
+   copied                = strlcpy(path_buf, name, sizeof(path_buf));
+
+   /* Non-NT platforms don't like extra slashes in the path */
+   if (name[path_len - 1] != '\\')
+      path_buf[copied++]   = '\\';
+
+   path_buf[copied]        = '*';
+   path_buf[copied+1]      = '\0';
+
+#if defined(LEGACY_WIN32)
+   path_local              = utf8_to_local_string_alloc(path_buf);
+   rdir->directory         = FindFirstFile(path_local, &rdir->entry);
+
+   if (path_local)
+      free(path_local);
+#else
+   path_wide               = utf8_to_utf16_string_alloc(path_buf);
+   rdir->directory         = FindFirstFileW(path_wide, &rdir->entry);
+
+   if (path_wide)
+      free(path_wide);
+#endif
+
+#elif defined(VITA) || defined(PSP)
+   rdir->directory       = sceIoDopen(name);
+#elif defined(_3DS)
+   rdir->directory       = !string_is_empty(name) ? opendir(name) : NULL;
+   rdir->entry           = NULL;
+#elif defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
+   rdir->error           = cellFsOpendir(name, &rdir->directory);
+#elif defined(ORBIS)
+   rdir->directory       = orbisDopen(name);
+#else
+   rdir->directory       = opendir(name);
+   rdir->entry           = NULL;
+#endif
+
+#ifdef _WIN32
+   if (include_hidden)
+      rdir->entry.dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;
+   else
+      rdir->entry.dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN;
+#endif
+
+   if (rdir->directory && !dirent_check_error(rdir))
+      return rdir;
+
+   retro_vfs_closedir_impl(rdir);
+   return NULL;
+}
+
+bool retro_vfs_readdir_impl(libretro_vfs_implementation_dir *rdir)
+{
+#if defined(_WIN32)
+   if (rdir->next)
+#if defined(LEGACY_WIN32)
+      return (FindNextFile(rdir->directory, &rdir->entry) != 0);
+#else
+      return (FindNextFileW(rdir->directory, &rdir->entry) != 0);
+#endif
+
+   rdir->next = true;
+   return (rdir->directory != INVALID_HANDLE_VALUE);
+#elif defined(VITA) || defined(PSP)
+   return (sceIoDread(rdir->directory, &rdir->entry) > 0);
+#elif defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
+   uint64_t nread;
+   rdir->error = cellFsReaddir(rdir->directory, &rdir->entry, &nread);
+   return (nread != 0);
+#elif defined(ORBIS)
+   return (orbisDread(rdir->directory, &rdir->entry) > 0);
+#else
+   return ((rdir->entry = readdir(rdir->directory)) != NULL);
+#endif
+}
+
+const char *retro_vfs_dirent_get_name_impl(libretro_vfs_implementation_dir *rdir)
+{
+#if defined(_WIN32)
+#if defined(LEGACY_WIN32)
+   char *name       = local_to_utf8_string_alloc(rdir->entry.cFileName);
+#else
+   char *name       = utf16_to_utf8_string_alloc(rdir->entry.cFileName);
+#endif
+   memset(rdir->entry.cFileName, 0, sizeof(rdir->entry.cFileName));
+   strlcpy((char*)rdir->entry.cFileName, name, sizeof(rdir->entry.cFileName));
+   if (name)
+      free(name);
+   return (char*)rdir->entry.cFileName;
+#elif defined(VITA) || defined(PSP) || defined(__CELLOS_LV2__) && !defined(__PSL1GHT__) || defined(ORBIS)
+   return rdir->entry.d_name;
+#else
+   if (!rdir || !rdir->entry)
+      return NULL;
+   return rdir->entry->d_name;
+#endif
+}
+
+bool retro_vfs_dirent_is_dir_impl(libretro_vfs_implementation_dir *rdir)
+{
+#if defined(_WIN32)
+   const WIN32_FIND_DATA *entry = (const WIN32_FIND_DATA*)&rdir->entry;
+   return entry->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
+#elif defined(PSP) || defined(VITA)
+   const SceIoDirent *entry     = (const SceIoDirent*)&rdir->entry;
+#if defined(PSP)
+   return (entry->d_stat.st_attr & FIO_SO_IFDIR) == FIO_SO_IFDIR;
+#elif defined(VITA)
+   return SCE_S_ISDIR(entry->d_stat.st_mode);
+#endif
+#elif defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
+   CellFsDirent *entry          = (CellFsDirent*)&rdir->entry;
+   return (entry->d_type == CELL_FS_TYPE_DIRECTORY);
+#elif defined(ORBIS)
+   const struct dirent *entry   = &rdir->entry;
+   if (entry->d_type == DT_DIR)
+      return true;
+   if (!(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK))
+      return false;
+#else
+   struct stat buf;
+   char path[PATH_MAX_LENGTH];
+#if defined(DT_DIR)
+   const struct dirent *entry = (const struct dirent*)rdir->entry;
+   if (entry->d_type == DT_DIR)
+      return true;
+   /* This can happen on certain file systems. */
+   if (!(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK))
+      return false;
+#endif
+   /* dirent struct doesn't have d_type, do it the slow way ... */
+   path[0] = '\0';
+   fill_pathname_join(path, rdir->orig_path, retro_vfs_dirent_get_name_impl(rdir), sizeof(path));
+   if (stat(path, &buf) < 0)
+      return false;
+   return S_ISDIR(buf.st_mode);
+#endif
+}
+
+int retro_vfs_closedir_impl(libretro_vfs_implementation_dir *rdir)
+{
+   if (!rdir)
+      return -1;
+
+#if defined(_WIN32)
+   if (rdir->directory != INVALID_HANDLE_VALUE)
+      FindClose(rdir->directory);
+#elif defined(VITA) || defined(PSP)
+   sceIoDclose(rdir->directory);
+#elif defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
+   rdir->error = cellFsClosedir(rdir->directory);
+#elif defined(ORBIS)
+   orbisDclose(rdir->directory);
+#else
+   if (rdir->directory)
+      closedir(rdir->directory);
+#endif
+
+   if (rdir->orig_path)
+      free(rdir->orig_path);
+   free(rdir);
+   return 0;
+}
index 007e96b..9a74908 100644 (file)
@@ -8,8 +8,6 @@
  * See the COPYING file in the top-level directory.
  */
 
-#include <compat/fopen_utf8.h>
-
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
@@ -323,7 +321,7 @@ static long handle_eboot(void)
        int i, ret;
        FILE *f;
 
-       f = fopen_utf8(cd_fname, "rb");
+       f = fopen(cd_fname, "rb");
        if (f == NULL) {
                err("missing file: %s: ", cd_fname);
                perror(NULL);
@@ -419,6 +417,8 @@ static long CDRopen(void)
        char *ext;
        FILE *f = NULL;
 
+       printf("%s cd_file=%d\n", __func__, cd_file == NULL ? 0 : 1);
+
        if (cd_file != NULL)
                return 0; // it's already open
 
@@ -462,7 +462,7 @@ static long CDRopen(void)
                return -1;
        }
 
-       f = fopen_utf8(table_fname, "rb");
+       f = fopen(table_fname, "rb");
        if (f == NULL) {
                err("missing file: %s: ", table_fname);
                perror(NULL);
@@ -527,7 +527,7 @@ static long CDRopen(void)
                break;
        }
 
-       cd_file = fopen_utf8(cd_fname, "rb");
+       cd_file = fopen(cd_fname, "rb");
        if (cd_file == NULL) {
                err("failed to open: %s: ", table_fname);
                perror(NULL);