diff --git a/nx/Makefile b/nx/Makefile
index e8ec3f88..85e1990a 100644
--- a/nx/Makefile
+++ b/nx/Makefile
@@ -24,7 +24,7 @@ VERSION	:=	$(LIBNX_MAJOR).$(LIBNX_MINOR).$(LIBNX_PATCH)
 #---------------------------------------------------------------------------------
 TARGET		:=	nx
 #BUILD		:=	build
-SOURCES		:=	source/arm source/system source/kernel source/services source/gfx source/devices
+SOURCES		:=	source/arm source/system source/kernel source/services source/gfx source/devices source/util/utf
 
 DATA		:=	data
 INCLUDES	:=	include
diff --git a/nx/include/switch.h b/nx/include/switch.h
index 9cfb5754..448f1513 100644
--- a/nx/include/switch.h
+++ b/nx/include/switch.h
@@ -21,6 +21,8 @@ extern "C" {
 #include <switch/kernel/virtmem.h>
 #include <switch/kernel/version.h>
 
+#include <switch/util/utf.h>
+
 #include <switch/services/sm.h>
 #include <switch/services/fs.h>
 #include <switch/services/applet.h>
@@ -38,6 +40,7 @@ extern "C" {
 #include <switch/gfx/ioctl.h>
 
 #include <switch/devices/usb_comms.h>
+#include <switch/devices/fs_dev.h>
 
 #ifdef __cplusplus
 }
diff --git a/nx/include/switch/devices/fs_dev.h b/nx/include/switch/devices/fs_dev.h
new file mode 100644
index 00000000..e2f08473
--- /dev/null
+++ b/nx/include/switch/devices/fs_dev.h
@@ -0,0 +1,17 @@
+/**
+ * @file fs_dev.h
+ * @brief FS driver.
+ */
+#pragma once
+
+//NOTE: This is currently not usable.
+
+/// Initializes the FS driver.
+Result fsdevInit(void);
+
+/// Enable/disable copy in fsdev_write
+void fsdevWriteSafe(bool enable);
+
+/// Exits the FS driver.
+Result fsdevExit(void);
+
diff --git a/nx/include/switch/services/fs.h b/nx/include/switch/services/fs.h
index 228e10ab..6440c485 100644
--- a/nx/include/switch/services/fs.h
+++ b/nx/include/switch/services/fs.h
@@ -2,6 +2,8 @@
 
 // We use wrapped handles for type safety.
 
+#define FS_MAX_PATH 0x301
+
 typedef struct {
     Handle  h;
 } FsFileSystem;
@@ -18,12 +20,39 @@ typedef struct {
     Handle  h;
 } FsStorage;
 
+/// Directory entry.
+typedef struct
+{
+	char name[FS_MAX_PATH];      ///< Entry name.
+	u8 pad[3];
+	u32 attributes;       ///< Attributes.
+	u64 fileSize;         ///< File size.
+} FsDirectoryEntry;
+
 typedef enum {
     ENTRYTYPE_FILE=0,
     ENTRYTYPE_DIR =1
 } FsEntryType;
 
-#define FS_MAX_PATH 0x301
+typedef enum
+{
+	FS_OPEN_READ   = BIT(0), ///< Open for reading.
+	FS_OPEN_WRITE  = BIT(1), ///< Open for writing.
+	FS_OPEN_APPEND = BIT(2), ///< Append file.
+} FsFileFlags;
+
+/// For use with fsFsOpenDirectory.
+typedef enum
+{
+	FS_DIROPEN_DIRECTORY   = BIT(0), ///< Enable reading directory entries.
+	FS_DIROPEN_FILE  = BIT(1),       ///< Enable reading file entries.
+} FsDirectoryFlags;
+
+/// Attribute flags.
+typedef enum
+{
+	FS_ATTRIBUTE_FILE = BIT(0),  ///< File.
+} FsAttribute;
 
 Result fsInitialize();
 void fsExit(void);
@@ -51,11 +80,15 @@ void fsFsClose(FsFileSystem* fs);
 
 // IFile
 Result fsFileRead(FsFile* f, u64 off, void* buf, size_t len, size_t* out);
-Result fsFileWrite(FsFile* f, u64 off, void* buf, size_t len, size_t* out);
+Result fsFileWrite(FsFile* f, u64 off, const void* buf, size_t len, size_t* out);
 Result fsFileFlush(FsFile* f);
 Result fsFileSetSize(FsFile* f, u64 sz);
 Result fsFileGetSize(FsFile* f, u64* out);
 void fsFileClose(FsFile* f);
 
-// todo: IDirectory
+// IDirectory
+Result fsDirRead(FsDir* d, u64 inval, size_t* total_entries, size_t max_entries, FsDirectoryEntry *buf);
+Result fsDirGetEntryCount(FsDir* d, u64* count);
+void fsDirClose(FsDir* d);
+
 // todo: IStorage
diff --git a/nx/include/switch/util/utf.h b/nx/include/switch/util/utf.h
new file mode 100644
index 00000000..8c4f914b
--- /dev/null
+++ b/nx/include/switch/util/utf.h
@@ -0,0 +1,156 @@
+/**
+ * @file utf.h
+ * @brief UTF conversion functions.
+ */
+#pragma once
+
+#include <stdint.h>
+#include <sys/types.h>
+
+/** Convert a UTF-8 sequence into a UTF-32 codepoint
+ *
+ *  @param[out] out Output codepoint
+ *  @param[in]  in  Input sequence
+ *
+ *  @returns number of input code units consumed
+ *  @returns -1 for error
+ */
+ssize_t decode_utf8 (uint32_t *out, const uint8_t *in);
+
+/** Convert a UTF-16 sequence into a UTF-32 codepoint
+ *
+ *  @param[out] out Output codepoint
+ *  @param[in]  in  Input sequence
+ *
+ *  @returns number of input code units consumed
+ *  @returns -1 for error
+ */
+ssize_t decode_utf16(uint32_t *out, const uint16_t *in);
+
+/** Convert a UTF-32 codepoint into a UTF-8 sequence
+ *
+ *  @param[out] out Output sequence
+ *  @param[in]  in  Input codepoint
+ *
+ *  @returns number of output code units produced
+ *  @returns -1 for error
+ *
+ *  @note \a out must be able to store 4 code units
+ */
+ssize_t encode_utf8 (uint8_t *out, uint32_t in);
+
+/** Convert a UTF-32 codepoint into a UTF-16 sequence
+ *
+ *  @param[out] out Output sequence
+ *  @param[in]  in  Input codepoint
+ *
+ *  @returns number of output code units produced
+ *  @returns -1 for error
+ *
+ *  @note \a out must be able to store 2 code units
+ */
+ssize_t encode_utf16(uint16_t *out, uint32_t in);
+
+/** Convert a UTF-8 sequence into a UTF-16 sequence
+ *
+ *  Fills the output buffer up to \a len code units.
+ *  Returns the number of code units that the input would produce;
+ *  if it returns greater than \a len, the output has been
+ *  truncated.
+ *
+ *  @param[out] out Output sequence
+ *  @param[in]  in  Input sequence (null-terminated)
+ *  @param[in]  len Output length
+ *
+ *  @returns number of output code units produced
+ *  @returns -1 for error
+ *
+ *  @note \a out is not null-terminated
+ */
+ssize_t utf8_to_utf16(uint16_t *out, const uint8_t  *in, size_t len);
+
+/** Convert a UTF-8 sequence into a UTF-32 sequence
+ *
+ *  Fills the output buffer up to \a len code units.
+ *  Returns the number of code units that the input would produce;
+ *  if it returns greater than \a len, the output has been
+ *  truncated.
+ *
+ *  @param[out] out Output sequence
+ *  @param[in]  in  Input sequence (null-terminated)
+ *  @param[in]  len Output length
+ *
+ *  @returns number of output code units produced
+ *  @returns -1 for error
+ *
+ *  @note \a out is not null-terminated
+ */
+ssize_t utf8_to_utf32(uint32_t *out, const uint8_t  *in, size_t len);
+
+/** Convert a UTF-16 sequence into a UTF-8 sequence
+ *
+ *  Fills the output buffer up to \a len code units.
+ *  Returns the number of code units that the input would produce;
+ *  if it returns greater than \a len, the output has been
+ *  truncated.
+ *
+ *  @param[out] out Output sequence
+ *  @param[in]  in  Input sequence (null-terminated)
+ *  @param[in]  len Output length
+ *
+ *  @returns number of output code units produced
+ *  @returns -1 for error
+ *
+ *  @note \a out is not null-terminated
+ */
+ssize_t utf16_to_utf8(uint8_t  *out, const uint16_t *in, size_t len);
+
+/** Convert a UTF-16 sequence into a UTF-32 sequence
+ *
+ *  Fills the output buffer up to \a len code units.
+ *  Returns the number of code units that the input would produce;
+ *  if it returns greater than \a len, the output has been
+ *  truncated.
+ *
+ *  @param[out] out Output sequence
+ *  @param[in]  in  Input sequence (null-terminated)
+ *  @param[in]  len Output length
+ *
+ *  @returns number of output code units produced
+ *  @returns -1 for error
+ *
+ *  @note \a out is not null-terminated
+ */
+ssize_t utf16_to_utf32(uint32_t *out, const uint16_t *in, size_t len);
+
+/** Convert a UTF-32 sequence into a UTF-8 sequence
+ *
+ *  Fills the output buffer up to \a len code units.
+ *  Returns the number of code units that the input would produce;
+ *  if it returns greater than \a len, the output has been
+ *  truncated.
+ *
+ *  @param[out] out Output sequence
+ *  @param[in]  in  Input sequence (null-terminated)
+ *  @param[in]  len Output length
+ *
+ *  @returns number of output code units produced
+ *  @returns -1 for error
+ *
+ *  @note \a out is not null-terminated
+ */
+ssize_t utf32_to_utf8(uint8_t  *out, const uint32_t *in, size_t len);
+
+/** Convert a UTF-32 sequence into a UTF-16 sequence
+ *
+ *  @param[out] out Output sequence
+ *  @param[in]  in  Input sequence (null-terminated)
+ *  @param[in]  len Output length
+ *
+ *  @returns number of output code units produced
+ *  @returns -1 for error
+ *
+ *  @note \a out is not null-terminated
+ */
+ssize_t utf32_to_utf16(uint16_t *out, const uint32_t *in, size_t len);
+
diff --git a/nx/source/devices/fs_dev.c b/nx/source/devices/fs_dev.c
new file mode 100644
index 00000000..f6a1ffaa
--- /dev/null
+++ b/nx/source/devices/fs_dev.c
@@ -0,0 +1,1436 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/dirent.h>
+#include <sys/iosupport.h>
+#include <sys/param.h>
+#include <unistd.h>
+
+#include <switch.h>
+
+
+/*! @internal
+ *
+ *  @file fsdev_dev.c
+ *
+ *  FS Device
+ */
+
+static int fsdev_translate_error(Result error);
+
+static int       fsdev_open(struct _reent *r, void *fileStruct, const char *path, int flags, int mode);
+static int       fsdev_close(struct _reent *r, void *fd);
+static ssize_t   fsdev_write(struct _reent *r, void *fd, const char *ptr, size_t len);
+static ssize_t   fsdev_write_safe(struct _reent *r, void *fd, const char *ptr, size_t len);
+static ssize_t   fsdev_read(struct _reent *r, void *fd, char *ptr, size_t len);
+static off_t     fsdev_seek(struct _reent *r, void *fd, off_t pos, int dir);
+static int       fsdev_fstat(struct _reent *r, void *fd, struct stat *st);
+static int       fsdev_stat(struct _reent *r, const char *file, struct stat *st);
+static int       fsdev_link(struct _reent *r, const char *existing, const char  *newLink);
+static int       fsdev_unlink(struct _reent *r, const char *name);
+static int       fsdev_chdir(struct _reent *r, const char *name);
+static int       fsdev_rename(struct _reent *r, const char *oldName, const char *newName);
+static int       fsdev_mkdir(struct _reent *r, const char *path, int mode);
+static DIR_ITER* fsdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path);
+static int       fsdev_dirreset(struct _reent *r, DIR_ITER *dirState);
+static int       fsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat);
+static int       fsdev_dirclose(struct _reent *r, DIR_ITER *dirState);
+static int       fsdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf);
+static int       fsdev_ftruncate(struct _reent *r, void *fd, off_t len);
+static int       fsdev_fsync(struct _reent *r, void *fd);
+static int       fsdev_chmod(struct _reent *r, const char *path, mode_t mode);
+static int       fsdev_fchmod(struct _reent *r, void *fd, mode_t mode);
+static int       fsdev_rmdir(struct _reent *r, const char *name);
+
+/*! @cond INTERNAL */
+
+/*! Open file struct */
+typedef struct
+{
+  FsFile fd;
+  int    flags;  /*! Flags used in open(2) */
+  u64    offset; /*! Current file offset */
+} fsdev_file_t;
+
+#define FSDEV_DIRITER_MAGIC 0x66736476 /* "fsdv" */
+
+/*! Open directory struct */
+typedef struct
+{
+  u32               magic;          /*! "fsdv" */
+  FsDir             fd;
+  ssize_t           index;          /*! Current entry index */
+  size_t            size;           /*! Current batch size */
+  FsDirectoryEntry entry_data[32]; /*! Temporary storage for reading entries */
+} fsdev_dir_t;
+
+/*! fsdev devoptab */
+static devoptab_t
+fsdev_devoptab =
+{
+  .structSize   = sizeof(fsdev_file_t),
+  .open_r       = fsdev_open,
+  .close_r      = fsdev_close,
+  .write_r      = fsdev_write,
+  .read_r       = fsdev_read,
+  .seek_r       = fsdev_seek,
+  .fstat_r      = fsdev_fstat,
+  .stat_r       = fsdev_stat,
+  .link_r       = fsdev_link,
+  .unlink_r     = fsdev_unlink,
+  .chdir_r      = fsdev_chdir,
+  .rename_r     = fsdev_rename,
+  .mkdir_r      = fsdev_mkdir,
+  .dirStateSize = sizeof(fsdev_dir_t),
+  .diropen_r    = fsdev_diropen,
+  .dirreset_r   = fsdev_dirreset,
+  .dirnext_r    = fsdev_dirnext,
+  .dirclose_r   = fsdev_dirclose,
+  .statvfs_r    = fsdev_statvfs,
+  .ftruncate_r  = fsdev_ftruncate,
+  .fsync_r      = fsdev_fsync,
+  .deviceData   = 0,
+  .chmod_r      = fsdev_chmod,
+  .fchmod_r     = fsdev_fchmod,
+  .rmdir_r      = fsdev_rmdir,
+};
+
+typedef struct
+{
+    bool setup;
+    devoptab_t device;
+    FsFileSystem fs;
+    char name[32];
+} fsdev_fsdevice;
+
+static fsdev_fsdevice fsdev_fsdevices[32];
+
+/*! @endcond */
+
+static char     __cwd[PATH_MAX+1] = "/";
+static __thread char     __fixedpath[PATH_MAX+1];
+//static __thread uint16_t __utf16path[PATH_MAX+1];
+
+static fsdev_fsdevice *fsdevFindDevice(const char *name)
+{
+  u32 i;
+  u32 total = sizeof(fsdev_fsdevices) / sizeof(fsdev_fsdevice);
+  fsdev_fsdevice *device = NULL;
+
+  for(i=0; i<total; i++)
+  {
+    device = &fsdev_fsdevices[i];
+
+    if(name==NULL) //Find an unused device entry.
+    {
+      if(!device->setup)
+        return device;
+    }
+    else if(device->setup) //Find the device with the input name.
+    {
+      if(strncmp(device->name, name, strlen(device->name))==0)
+        return device;
+    }
+  }
+
+  return NULL;
+}
+
+static const char*
+fsdev_fixpath(struct _reent *r,
+             const char    *path,
+             fsdev_fsdevice **device)
+{
+  ssize_t       units;
+  uint32_t      code;
+  const uint8_t *p = (const uint8_t*)path;
+
+  if(device)
+    *device = fsdevFindDevice(path);
+
+  // Move the path pointer to the start of the actual path
+  do
+  {
+    units = decode_utf8(&code, p);
+    if(units < 0)
+    {
+      r->_errno = EILSEQ;
+      return NULL;
+    }
+
+    p += units;
+  } while(code != ':' && code != 0);
+
+  // We found a colon; p points to the actual path
+  if(code == ':')
+    path = (const char*)p;
+
+  // Make sure there are no more colons and that the
+  // remainder of the filename is valid UTF-8
+  p = (const uint8_t*)path;
+  do
+  {
+    units = decode_utf8(&code, p);
+    if(units < 0)
+    {
+      r->_errno = EILSEQ;
+      return NULL;
+    }
+
+    if(code == ':')
+    {
+      r->_errno = EINVAL;
+      return NULL;
+    }
+
+    p += units;
+  } while(code != 0);
+
+  if(path[0] == '/')
+    strncpy(__fixedpath, path, PATH_MAX);
+  else
+  {
+    strncpy(__fixedpath, __cwd, PATH_MAX);
+    strncat(__fixedpath, path, PATH_MAX);
+  }
+
+  if(__fixedpath[PATH_MAX] != 0)
+  {
+    __fixedpath[PATH_MAX] = 0;
+    r->_errno = ENAMETOOLONG;
+    return NULL;
+  }
+
+  return __fixedpath;
+}
+
+static int
+fsdev_getfspath(struct _reent *r,
+               const char     *path,
+               fsdev_fsdevice **device,
+               char           *outpath)
+{
+  //ssize_t units;
+
+  if(fsdev_fixpath(r, path, device) == NULL)
+    return -1;
+
+  //TODO: What encoding does FS paths use?
+
+  /*units = utf8_to_utf16(__utf16path, (const uint8_t*)__fixedpath, PATH_MAX);
+  if(units < 0)
+  {
+    r->_errno = EILSEQ;
+    return fspath;
+  }
+  if(units >= PATH_MAX)
+  {
+    r->_errno = ENAMETOOLONG;
+    return fspath;
+  }
+
+  __utf16path[units] = 0;*/
+
+  memset(outpath, 0, FS_MAX_PATH);
+  strncpy(outpath, __fixedpath, FS_MAX_PATH);
+
+  return 0;
+}
+
+static ssize_t fsdev_convertfromfspath(uint8_t *out, uint8_t *in, size_t len)
+{
+  //TODO: What encoding does FS paths use?
+
+  strncpy((char*)out, (char*)in, len);
+  return strnlen((char*)out, len);
+}
+
+extern int __system_argc;
+extern char** __system_argv;
+
+static bool fsdevInitialised = false;
+
+static void fsdevUpdateDevices(void)
+{
+  u32 i;
+  u32 total = sizeof(fsdev_fsdevices) / sizeof(fsdev_fsdevice);
+
+  for(i=0; i<total; i++)
+  {
+    memcpy(&fsdev_fsdevices[i].device, &fsdev_devoptab, sizeof(fsdev_devoptab));
+    fsdev_fsdevices[i].device.name = fsdev_fsdevices[i].name;
+  }
+}
+
+int fsdevMountDevice(const char *name, FsFileSystem fs)
+{
+  fsdev_fsdevice *device = fsdevFindDevice(NULL);
+
+  if(device==NULL)
+  {
+    fsFsClose(&fs);
+    return -1;
+  }
+
+  device->fs = fs;
+  memset(device->name, 0, sizeof(device->name));
+  strncpy(device->name, name, sizeof(device->name)-1);
+
+  int dev = AddDevice(&device->device);
+  if(dev==-1)
+  {
+    fsFsClose(&device->fs);
+    return dev;
+  }
+
+  device->setup = 1;
+
+  return dev;
+}
+
+static int _fsdevUnmountDeviceStruct(fsdev_fsdevice *device)
+{
+  if(!device->setup)
+    return 0;
+
+  RemoveDevice(device->name);
+  fsFsClose(&device->fs);
+
+  memset(device, 0, sizeof(fsdev_fsdevice));
+
+  return 0;
+}
+
+int fsdevUnmountDevice(const char *name)
+{
+  fsdev_fsdevice *device;
+
+  device = fsdevFindDevice(name);
+  if(device==NULL)
+    return -1;
+
+  return _fsdevUnmountDeviceStruct(device);
+}
+
+/*! Initialize SDMC device */
+Result fsdevInit(void)
+{
+  /*ssize_t  units;
+  uint32_t code;
+  char     *p;*/
+  Result   rc = 0;
+  FsFileSystem fs;
+
+  if(fsdevInitialised)
+    return rc;
+
+  memset(fsdev_fsdevices, 0, sizeof(fsdev_fsdevices));
+  fsdevUpdateDevices();
+
+  rc = fsMountSdcard(&fs);
+  if(R_SUCCEEDED(rc))
+  {
+    int dev = fsdevMountDevice("sdmc", fs);
+
+    if(dev != -1)
+    {
+      setDefaultDevice(dev);
+      //TODO: Re-enable this once __system_argc/__system_argv are actually defined.
+      /*if(__system_argc != 0 && __system_argv[0] != NULL)
+      {
+        if(FindDevice(__system_argv[0]) == dev)
+        {
+          strncpy(__fixedpath,__system_argv[0],PATH_MAX);
+          if(__fixedpath[PATH_MAX] != 0)
+          {
+            __fixedpath[PATH_MAX] = 0;
+          }
+          else
+          {
+            char *last_slash = NULL;
+            p = __fixedpath;
+            do
+            {
+              units = decode_utf8(&code, (const uint8_t*)p);
+              if(units < 0)
+              {
+                last_slash = NULL;
+                break;
+              }
+
+              if(code == '/')
+                last_slash = p;
+
+              p += units;
+            } while(code != 0);
+
+            if(last_slash != NULL)
+            {
+              last_slash[0] = 0;
+              chdir(__fixedpath);
+            }
+          }
+        }
+      }*/
+    }
+  }
+
+  fsdevInitialised = true;
+
+  return rc;
+}
+
+/*! Enable/disable safe fsdev_write
+ *
+ *  Safe fsdev_write is enabled by default. If it is disabled, you will be
+ *  unable to write from read-only buffers.
+ *
+ *  @param[in] enable Whether to enable
+ */
+void fsdevWriteSafe(bool enable)
+{
+  if(enable)
+    fsdev_devoptab.write_r = fsdev_write_safe;
+  else
+    fsdev_devoptab.write_r = fsdev_write;
+
+  fsdevUpdateDevices();
+}
+
+/*! Clean up fsdev devices */
+Result fsdevExit(void)
+{
+  u32 i;
+  u32 total = sizeof(fsdev_fsdevices) / sizeof(fsdev_fsdevice);
+  Result rc=0;
+
+  if(!fsdevInitialised) return rc;
+
+  for(i=0; i<total; i++)
+  {
+    _fsdevUnmountDeviceStruct(&fsdev_fsdevices[i]);
+  }
+
+  fsdevInitialised = false;
+
+  return 0;
+}
+
+/*! Open a file
+ *
+ *  @param[in,out] r          newlib reentrancy struct
+ *  @param[out]    fileStruct Pointer to file struct to fill in
+ *  @param[in]     path       Path to open
+ *  @param[in]     flags      Open flags from open(2)
+ *  @param[in]     mode       Permissions to set on create
+ *
+ *  @returns 0 for success
+ *  @returns -1 for error
+ */
+static int
+fsdev_open(struct _reent *r,
+          void          *fileStruct,
+          const char    *path,
+          int           flags,
+          int           mode)
+{
+  FsFile        fd;
+  Result        rc;
+  u32           fsdev_flags = 0;
+  u32           attributes = 0;
+  char          fs_path[FS_MAX_PATH];
+  fsdev_fsdevice *device = NULL;
+return -1;
+  if(fsdev_getfspath(r, path, &device, fs_path)==-1)
+    return -1;
+
+  /* get pointer to our data */
+  fsdev_file_t *file = (fsdev_file_t*)fileStruct;
+
+  /* check access mode */
+  switch(flags & O_ACCMODE)
+  {
+    /* read-only: do not allow O_APPEND */
+    case O_RDONLY:
+      fsdev_flags |= FS_OPEN_READ;
+      if(flags & O_APPEND)
+      {
+        r->_errno = EINVAL;
+        return -1;
+      }
+      break;
+
+    /* write-only */
+    case O_WRONLY:
+      fsdev_flags |= FS_OPEN_WRITE;
+      break;
+
+    /* read and write */
+    case O_RDWR:
+      fsdev_flags |= (FS_OPEN_READ | FS_OPEN_WRITE);
+      break;
+
+    /* an invalid option was supplied */
+    default:
+      r->_errno = EINVAL;
+      return -1;
+  }
+
+  /* Test O_EXCL. */
+  if((flags & O_CREAT) && (flags & O_EXCL))
+  {
+    rc = fsFsCreateFile(&device->fs, fs_path, 0, attributes);
+    if(R_FAILED(rc))
+    {
+      r->_errno = fsdev_translate_error(rc);
+      return -1;
+    }
+  }
+
+  /* set attributes */
+  /*if(!(mode & S_IWUSR))
+    attributes |= FS_ATTRIBUTE_READONLY;*/
+
+  /* open the file */
+  rc = fsFsOpenFile(&device->fs, fs_path, fsdev_flags, &fd);
+  if(R_SUCCEEDED(rc))
+  {
+    if((flags & O_ACCMODE) != O_RDONLY && (flags & O_TRUNC))
+    {
+      rc = fsFileSetSize(&fd, 0);
+      if(R_FAILED(rc))
+      {
+        fsFileClose(&fd);
+        r->_errno = fsdev_translate_error(rc);
+        return -1;
+      }
+    }
+
+    file->fd     = fd;
+    file->flags  = (flags & (O_ACCMODE|O_APPEND|O_SYNC));
+    file->offset = 0;
+    return 0;
+  }
+
+  r->_errno = fsdev_translate_error(rc);
+  return -1;
+}
+
+/*! Close an open file
+ *
+ *  @param[in,out] r  newlib reentrancy struct
+ *  @param[in]     fd Pointer to fsdev_file_t
+ *
+ *  @returns 0 for success
+ *  @returns -1 for error
+ */
+static int
+fsdev_close(struct _reent *r,
+           void          *fd)
+{
+  Result      rc=0;
+
+  /* get pointer to our data */
+  fsdev_file_t *file = (fsdev_file_t*)fd;
+
+  fsFileClose(&file->fd);
+  if(R_SUCCEEDED(rc))
+    return 0;
+
+  r->_errno = fsdev_translate_error(rc);
+  return -1;
+}
+
+/*! Write to an open file
+ *
+ *  @param[in,out] r   newlib reentrancy struct
+ *  @param[in,out] fd  Pointer to fsdev_file_t
+ *  @param[in]     ptr Pointer to data to write
+ *  @param[in]     len Length of data to write
+ *
+ *  @returns number of bytes written
+ *  @returns -1 for error
+ */
+static ssize_t
+fsdev_write(struct _reent *r,
+           void          *fd,
+           const char    *ptr,
+           size_t        len)
+{
+  Result      rc;
+  size_t      bytes;
+
+  /* get pointer to our data */
+  fsdev_file_t *file = (fsdev_file_t*)fd;
+
+  /* check that the file was opened with write access */
+  if((file->flags & O_ACCMODE) == O_RDONLY)
+  {
+    r->_errno = EBADF;
+    return -1;
+  }
+
+  if(file->flags & O_APPEND)
+  {
+    /* append means write from the end of the file */
+    rc = fsFileGetSize(&file->fd, &file->offset);
+    if(R_FAILED(rc))
+    {
+      r->_errno = fsdev_translate_error(rc);
+      return -1;
+    }
+  }
+
+  rc = fsFileWrite(&file->fd, file->offset, ptr, len, &bytes);
+  if(R_FAILED(rc))
+  {
+    r->_errno = fsdev_translate_error(rc);
+    return -1;
+  }
+
+  file->offset += bytes;
+
+  /* check if this is synchronous or not */
+  if(file->flags & O_SYNC)
+    fsFileFlush(&file->fd);
+
+  return bytes;
+}
+
+/*! Write to an open file
+ *
+ *  @param[in,out] r   newlib reentrancy struct
+ *  @param[in,out] fd  Pointer to fsdev_file_t
+ *  @param[in]     ptr Pointer to data to write
+ *  @param[in]     len Length of data to write
+ *
+ *  @returns number of bytes written
+ *  @returns -1 for error
+ */
+static ssize_t
+fsdev_write_safe(struct _reent *r,
+                void          *fd,
+                const char    *ptr,
+                size_t        len)
+{
+  Result      rc;
+  size_t      bytes, bytesWritten = 0;
+
+  /* get pointer to our data */
+  fsdev_file_t *file = (fsdev_file_t*)fd;
+
+  /* check that the file was opened with write access */
+  if((file->flags & O_ACCMODE) == O_RDONLY)
+  {
+    r->_errno = EBADF;
+    return -1;
+  }
+
+  if(file->flags & O_APPEND)
+  {
+    /* append means write from the end of the file */
+    rc = fsFileGetSize(&file->fd, &file->offset);
+    if(R_FAILED(rc))
+    {
+      r->_errno = fsdev_translate_error(rc);
+      return -1;
+    }
+  }
+
+  /* Copy to internal buffer and write in chunks.
+   * You cannot write from read-only memory.
+   */
+  static __thread char tmp_buffer[8192];
+  while(len > 0)
+  {
+    size_t toWrite = len;
+    if(toWrite > sizeof(tmp_buffer))
+      toWrite = sizeof(tmp_buffer);
+
+    /* copy to internal buffer */
+    memcpy(tmp_buffer, ptr, toWrite);
+
+    /* write the data */
+    rc = fsFileWrite(&file->fd, file->offset, tmp_buffer, toWrite, &bytes);
+
+    if(R_FAILED(rc))
+    {
+      /* return partial transfer */
+      if(bytesWritten > 0)
+        return bytesWritten;
+
+      r->_errno = fsdev_translate_error(rc);
+      return -1;
+    }
+
+    /* check if this is synchronous or not */
+    if(file->flags & O_SYNC)
+      fsFileFlush(&file->fd);
+
+    file->offset += bytes;
+    bytesWritten += bytes;
+    ptr          += bytes;
+    len          -= bytes;
+  }
+
+  return bytesWritten;
+}
+
+/*! Read from an open file
+ *
+ *  @param[in,out] r   newlib reentrancy struct
+ *  @param[in,out] fd  Pointer to fsdev_file_t
+ *  @param[out]    ptr Pointer to buffer to read into
+ *  @param[in]     len Length of data to read
+ *
+ *  @returns number of bytes read
+ *  @returns -1 for error
+ */
+static ssize_t
+fsdev_read(struct _reent *r,
+          void          *fd,
+          char          *ptr,
+          size_t         len)
+{
+  Result      rc;
+  size_t      bytes;
+
+  /* get pointer to our data */
+  fsdev_file_t *file = (fsdev_file_t*)fd;
+
+  /* check that the file was opened with read access */
+  if((file->flags & O_ACCMODE) == O_WRONLY)
+  {
+    r->_errno = EBADF;
+    return -1;
+  }
+
+  /* read the data */
+  rc = fsFileRead(&file->fd, file->offset, ptr, len, &bytes);
+  if(R_SUCCEEDED(rc))
+  {
+    /* update current file offset */
+    file->offset += bytes;
+    return (ssize_t)bytes;
+  }
+
+  r->_errno = fsdev_translate_error(rc);
+  return -1;
+}
+
+/*! Update an open file's current offset
+ *
+ *  @param[in,out] r      newlib reentrancy struct
+ *  @param[in,out] fd     Pointer to fsdev_file_t
+ *  @param[in]     pos    Offset to seek to
+ *  @param[in]     whence Where to seek from
+ *
+ *  @returns new offset for success
+ *  @returns -1 for error
+ */
+static off_t
+fsdev_seek(struct _reent *r,
+          void          *fd,
+          off_t         pos,
+          int           whence)
+{
+  Result      rc;
+  u64         offset;
+
+  /* get pointer to our data */
+  fsdev_file_t *file = (fsdev_file_t*)fd;
+
+  /* find the offset to see from */
+  switch(whence)
+  {
+    /* set absolute position; start offset is 0 */
+    case SEEK_SET:
+      offset = 0;
+      break;
+
+    /* set position relative to the current position */
+    case SEEK_CUR:
+      offset = file->offset;
+      break;
+
+    /* set position relative to the end of the file */
+    case SEEK_END:
+      rc = fsFileGetSize(&file->fd, &offset);
+      if(R_FAILED(rc))
+      {
+        r->_errno = fsdev_translate_error(rc);
+        return -1;
+      }
+      break;
+
+    /* an invalid option was provided */
+    default:
+      r->_errno = EINVAL;
+      return -1;
+  }
+
+  /* TODO: A better check that prevents overflow. */
+  if(pos < 0 && offset < -pos)
+  {
+    /* don't allow seek to before the beginning of the file */
+    r->_errno = EINVAL;
+    return -1;
+  }
+
+  /* update the current offset */
+  file->offset = offset + pos;
+  return file->offset;
+}
+
+/*! Get file stats from an open file
+ *
+ *  @param[in,out] r  newlib reentrancy struct
+ *  @param[in]     fd Pointer to fsdev_file_t
+ *  @param[out]    st Pointer to file stats to fill
+ *
+ *  @returns 0 for success
+ *  @returns -1 for error
+ */
+static int
+fsdev_fstat(struct _reent *r,
+           void          *fd,
+           struct stat   *st)
+{
+  Result      rc;
+  u64         size;
+  fsdev_file_t *file = (fsdev_file_t*)fd;
+
+  rc = fsFileGetSize(&file->fd, &size);
+  if(R_SUCCEEDED(rc))
+  {
+    memset(st, 0, sizeof(struct stat));
+    st->st_size = (off_t)size;
+    st->st_nlink = 1;
+    st->st_mode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
+    return 0;
+  }
+
+  r->_errno = fsdev_translate_error(rc);
+  return -1;
+}
+
+/*! Get file stats
+ *
+ *  @param[in,out] r    newlib reentrancy struct
+ *  @param[in]     file Path to file
+ *  @param[out]    st   Pointer to file stats to fill
+ *
+ *  @returns 0 for success
+ *  @returns -1 for error
+ */
+static int
+fsdev_stat(struct _reent *r,
+          const char    *file,
+          struct stat   *st)
+{
+  FsFile  fd;
+  FsDir   fdir;
+  Result  rc;
+  char    fs_path[FS_MAX_PATH];
+  fsdev_fsdevice *device = NULL;
+
+  if(fsdev_getfspath(r, file, &device, fs_path)==-1)
+    return -1;
+
+  if(R_SUCCEEDED(rc = fsFsOpenFile(&device->fs, fs_path, FS_OPEN_READ, &fd)))
+  {
+    fsdev_file_t tmpfd = { .fd = fd };
+    rc = fsdev_fstat(r, &tmpfd, st);
+    fsFileClose(&fd);
+
+    return rc;
+  }
+  else if(R_SUCCEEDED(rc = fsFsOpenDirectory(&device->fs, fs_path, FS_DIROPEN_DIRECTORY | FS_DIROPEN_FILE, &fdir)))
+  {
+    memset(st, 0, sizeof(struct stat));
+    st->st_nlink = 1;
+    st->st_mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO;
+    fsDirClose(&fdir);
+    return 0;
+  }
+
+  r->_errno = fsdev_translate_error(rc);
+  return -1;
+}
+
+/*! Hard link a file
+ *
+ *  @param[in,out] r        newlib reentrancy struct
+ *  @param[in]     existing Path of file to link
+ *  @param[in]     newLink  Path of new link
+ *
+ *  @returns 0 for success
+ *  @returns -1 for error
+ */
+static int
+fsdev_link(struct _reent *r,
+          const char    *existing,
+          const char    *newLink)
+{
+  r->_errno = ENOSYS;
+  return -1;
+}
+
+/*! Unlink a file
+ *
+ *  @param[in,out] r    newlib reentrancy struct
+ *  @param[in]     name Path of file to unlink
+ *
+ *  @returns 0 for success
+ *  @returns -1 for error
+ */
+static int
+fsdev_unlink(struct _reent *r,
+            const char    *name)
+{
+  Result  rc;
+  char    fs_path[FS_MAX_PATH];
+  fsdev_fsdevice *device = NULL;
+
+  if(fsdev_getfspath(r, name, &device, fs_path)==-1)
+    return -1;
+
+  rc = fsFsDeleteFile(&device->fs, fs_path);
+  if(R_SUCCEEDED(rc))
+    return 0;
+
+  r->_errno = fsdev_translate_error(rc);
+  return -1;
+}
+
+/*! Change current working directory
+ *
+ *  @param[in,out] r    newlib reentrancy struct
+ *  @param[in]     name Path to new working directory
+ *
+ *  @returns 0 for success
+ *  @returns -1 for error
+ */
+static int
+fsdev_chdir(struct _reent *r,
+           const char    *name)
+{
+  FsDir   fd;
+  Result  rc;
+  char    fs_path[FS_MAX_PATH];
+  fsdev_fsdevice *device = NULL;
+
+  if(fsdev_getfspath(r, name, &device, fs_path)==-1)
+    return -1;
+
+  rc = fsFsOpenDirectory(&device->fs, fs_path, FS_DIROPEN_DIRECTORY | FS_DIROPEN_FILE, &fd);
+  if(R_SUCCEEDED(rc))
+  {
+    fsDirClose(&fd);
+    strncpy(__cwd, __fixedpath, PATH_MAX);
+    return 0;
+  }
+
+  r->_errno = fsdev_translate_error(rc);
+  return -1;
+}
+
+/*! Rename a file
+ *
+ *  @param[in,out] r       newlib reentrancy struct
+ *  @param[in]     oldName Path to rename from
+ *  @param[in]     newName Path to rename to
+ *
+ *  @returns 0 for success
+ *  @returns -1 for error
+ */
+static int
+fsdev_rename(struct _reent *r,
+            const char    *oldName,
+            const char    *newName)
+{
+  Result  rc;
+  fsdev_fsdevice *device = NULL;
+  char fs_path_old[FS_MAX_PATH];
+  char fs_path_new[FS_MAX_PATH];
+
+  if(fsdev_getfspath(r, oldName, &device, fs_path_old)==-1)
+    return -1;
+
+  if(fsdev_getfspath(r, newName, NULL, fs_path_new)==-1)
+    return -1;
+
+  rc = fsFsRenameFile(&device->fs, fs_path_old, fs_path_new);
+  if(R_SUCCEEDED(rc))
+    return 0;
+
+  rc = fsFsRenameDirectory(&device->fs, fs_path_old, fs_path_new);
+  if(R_SUCCEEDED(rc))
+    return 0;
+
+  r->_errno = fsdev_translate_error(rc);
+  return -1;
+}
+
+/*! Create a directory
+ *
+ *  @param[in,out] r    newlib reentrancy struct
+ *  @param[in]     path Path of directory to create
+ *  @param[in]     mode Permissions of created directory
+ *
+ *  @returns 0 for success
+ *  @returns -1 for error
+ */
+static int
+fsdev_mkdir(struct _reent *r,
+           const char    *path,
+           int           mode)
+{
+  Result  rc;
+  char    fs_path[FS_MAX_PATH];
+  fsdev_fsdevice *device = NULL;
+
+  if(fsdev_getfspath(r, path, &device, fs_path)==-1)
+    return -1;
+
+  rc = fsFsCreateDirectory(&device->fs, fs_path);
+  if(R_SUCCEEDED(rc))
+    return 0;
+
+  r->_errno = fsdev_translate_error(rc);
+  return -1;
+}
+
+/*! Open a directory
+ *
+ *  @param[in,out] r        newlib reentrancy struct
+ *  @param[in]     dirState Pointer to open directory state
+ *  @param[in]     path     Path of directory to open
+ *
+ *  @returns dirState for success
+ *  @returns NULL for error
+ */
+static DIR_ITER*
+fsdev_diropen(struct _reent *r,
+             DIR_ITER      *dirState,
+             const char    *path)
+{
+  FsDir   fd;
+  Result  rc;
+  char    fs_path[FS_MAX_PATH];
+  fsdev_fsdevice *device = NULL;
+
+  if(fsdev_getfspath(r, path, &device, fs_path)==-1)
+    return NULL;
+
+  /* get pointer to our data */
+  fsdev_dir_t *dir = (fsdev_dir_t*)(dirState->dirStruct);
+
+  /* open the directory */
+  rc = fsFsOpenDirectory(&device->fs, fs_path, FS_DIROPEN_DIRECTORY | FS_DIROPEN_FILE, &fd);
+  if(R_SUCCEEDED(rc))
+  {
+    dir->magic = FSDEV_DIRITER_MAGIC;
+    dir->fd    = fd;
+    dir->index = -1;
+    dir->size  = 0;
+    memset(&dir->entry_data, 0, sizeof(dir->entry_data));
+    return dirState;
+  }
+
+  r->_errno = fsdev_translate_error(rc);
+  return NULL;
+}
+
+/*! Reset an open directory to its intial state
+ *
+ *  @param[in,out] r        newlib reentrancy struct
+ *  @param[in]     dirState Pointer to open directory state
+ *
+ *  @returns 0 for success
+ *  @returns -1 for error
+ */
+static int
+fsdev_dirreset(struct _reent *r,
+              DIR_ITER      *dirState)
+{
+  r->_errno = ENOSYS;
+  return -1;
+}
+
+/*! Fetch the next entry of an open directory
+ *
+ *  @param[in,out] r        newlib reentrancy struct
+ *  @param[in]     dirState Pointer to open directory state
+ *  @param[out]    filename Buffer to store entry name
+ *  @param[out]    filestat Buffer to store entry attributes
+ *
+ *  @returns 0 for success
+ *  @returns -1 for error
+ */
+static int
+fsdev_dirnext(struct _reent *r,
+             DIR_ITER      *dirState,
+             char          *filename,
+             struct stat   *filestat)
+{
+  Result              rc;
+  size_t              entries;
+  ssize_t             units;
+  FsDirectoryEntry   *entry;
+
+  /* get pointer to our data */
+  fsdev_dir_t *dir = (fsdev_dir_t*)(dirState->dirStruct);
+
+  static const size_t max_entries = sizeof(dir->entry_data) / sizeof(dir->entry_data[0]);
+
+  /* check if it's in the batch already */
+  if(++dir->index < dir->size)
+  {
+    rc = 0;
+  }
+  else
+  {
+    /* reset batch info */
+    dir->index = -1;
+    dir->size  = 0;
+
+    /* fetch the next batch */
+    memset(dir->entry_data, 0, sizeof(dir->entry_data));
+    rc = fsDirRead(&dir->fd, 0, &entries, max_entries, dir->entry_data);
+    if(R_SUCCEEDED(rc))
+    {
+      if(entries == 0)
+      {
+        /* there are no more entries; ENOENT signals end-of-directory */
+        r->_errno = ENOENT;
+        return -1;
+      }
+
+      dir->index = 0;
+      dir->size  = entries;
+    }
+  }
+
+  if(R_SUCCEEDED(rc))
+  {
+    entry = &dir->entry_data[dir->index];
+
+    /* fill in the stat info */
+    filestat->st_ino = 0;
+    if(entry->attributes & FS_ATTRIBUTE_FILE)
+      filestat->st_mode = S_IFREG;
+    else
+      filestat->st_mode = S_IFDIR;
+
+    /* convert name from UTF-16 to UTF-8 */
+    memset(filename, 0, NAME_MAX);
+    units = fsdev_convertfromfspath((uint8_t*)filename, (uint8_t*)entry->name, NAME_MAX);
+    if(units < 0)
+    {
+      r->_errno = EILSEQ;
+      return -1;
+    }
+
+    if(units >= NAME_MAX)
+    {
+      r->_errno = ENAMETOOLONG;
+      return -1;
+    }
+
+    return 0;
+  }
+
+  r->_errno = fsdev_translate_error(rc);
+  return -1;
+}
+
+/*! Close an open directory
+ *
+ *  @param[in,out] r        newlib reentrancy struct
+ *  @param[in]     dirState Pointer to open directory state
+ *
+ *  @returns 0 for success
+ *  @returns -1 for error
+ */
+static int
+fsdev_dirclose(struct _reent *r,
+              DIR_ITER      *dirState)
+{
+  Result         rc=0;
+
+  /* get pointer to our data */
+  fsdev_dir_t *dir = (fsdev_dir_t*)(dirState->dirStruct);
+
+  /* close the directory */
+  fsDirClose(&dir->fd);
+  if(R_SUCCEEDED(rc))
+    return 0;
+
+  r->_errno = fsdev_translate_error(rc);
+  return -1;
+}
+
+/*! Get filesystem statistics
+ *
+ *  @param[in,out] r    newlib reentrancy struct
+ *  @param[in]     path Path to filesystem to get statistics of
+ *  @param[out]    buf  Buffer to fill
+ *
+ *  @returns 0 for success
+ *  @returns -1 for error
+ */
+static int
+fsdev_statvfs(struct _reent  *r,
+             const char     *path,
+             struct statvfs *buf)
+{
+  Result rc=0;
+  //FS_ArchiveResource resource;
+  //bool writable = false;
+
+  //rc = FSUSER_GetSdmcArchiveResource(&resource);
+
+  if(R_SUCCEEDED(rc))
+  {
+    buf->f_bsize   = 0;//resource.clusterSize;
+    buf->f_frsize  = 0;//resource.clusterSize;
+    buf->f_blocks  = 0;//resource.totalClusters;
+    buf->f_bfree   = 0;//resource.freeClusters;
+    buf->f_bavail  = 0;//resource.freeClusters;
+    buf->f_files   = 0; //??? how to get
+    buf->f_ffree   = 0;//resource.freeClusters;
+    buf->f_favail  = 0;//resource.freeClusters;
+    buf->f_fsid    = 0; //??? how to get
+    buf->f_flag    = ST_NOSUID;
+    buf->f_namemax = 0; //??? how to get
+
+    /*rc = FSUSER_IsSdmcWritable(&writable);
+    if(R_FAILED(rc) || !writable)
+      buf->f_flag |= ST_RDONLY;*/
+
+    return 0;
+  }
+
+  r->_errno = fsdev_translate_error(rc);
+  return -1;
+}
+
+/*! Truncate an open file
+ *
+ *  @param[in,out] r   newlib reentrancy struct
+ *  @param[in]     fd  Pointer to fsdev_file_t
+ *  @param[in]     len Length to truncate file to
+ *
+ *  @returns 0 for success
+ *  @returns -1 for error
+ */
+static int
+fsdev_ftruncate(struct _reent *r,
+               void          *fd,
+               off_t         len)
+{
+  Result      rc;
+
+  /* get pointer to our data */
+  fsdev_file_t *file = (fsdev_file_t*)fd;
+
+  /* make sure length is non-negative */
+  if(len < 0)
+  {
+    r->_errno = EINVAL;
+    return -1;
+  }
+
+  /* set the new file size */
+  rc = fsFileSetSize(&file->fd, len);
+  if(R_SUCCEEDED(rc))
+    return 0;
+
+  r->_errno = fsdev_translate_error(rc);
+  return -1;
+}
+
+/*! Synchronize a file to media
+ *
+ *  @param[in,out] r  newlib reentrancy struct
+ *  @param[in]     fd Pointer to fsdev_file_t
+ *
+ *  @returns 0 for success
+ *  @returns -1 for error
+ */
+static int
+fsdev_fsync(struct _reent *r,
+           void          *fd)
+{
+  Result rc;
+
+  /* get pointer to our data */
+  fsdev_file_t *file = (fsdev_file_t*)fd;
+
+  rc = fsFileFlush(&file->fd);
+  if(R_SUCCEEDED(rc))
+    return 0;
+
+  r->_errno = fsdev_translate_error(rc);
+  return -1;
+}
+
+/*! Change a file's mode
+ *
+ *  @param[in,out] r    newlib reentrancy struct
+ *  @param[in]     path Path to file to update
+ *  @param[in]     mode New mode to set
+ *
+ *  @returns 0 for success
+ *  @returns -1 for error
+ */
+static int
+fsdev_chmod(struct _reent *r,
+          const char    *path,
+          mode_t        mode)
+{
+  r->_errno = ENOSYS;
+  return -1;
+}
+
+/*! Change an open file's mode
+ *
+ *  @param[in,out] r    newlib reentrancy struct
+ *  @param[in]     fd   Pointer to fsdev_file_t
+ *  @param[in]     mode New mode to set
+ *
+ *  @returns 0 for success
+ *  @returns -1 for failure
+ */
+static int
+fsdev_fchmod(struct _reent *r,
+            void          *fd,
+            mode_t        mode)
+{
+  r->_errno = ENOSYS;
+  return -1;
+}
+
+/*! Remove a directory
+ *
+ *  @param[in,out] r    newlib reentrancy struct
+ *  @param[in]     name Path of directory to remove
+ *
+ *  @returns 0 for success
+ *  @returns -1 for error
+ */
+static int
+fsdev_rmdir(struct _reent *r,
+           const char    *name)
+{
+  Result  rc;
+  char    fs_path[FS_MAX_PATH];
+  fsdev_fsdevice *device = NULL;
+
+  if(fsdev_getfspath(r, name, &device, fs_path)==-1)
+    return -1;
+
+  rc = fsFsDeleteDirectory(&device->fs, fs_path);
+  if(R_SUCCEEDED(rc))
+    return 0;
+
+  r->_errno = fsdev_translate_error(rc);
+  return -1;
+}
+
+Result
+fsdev_getmtime(const char *name,
+              u64        *mtime)
+{
+  //Result        rc;
+  struct _reent r;
+
+  r._errno = ENOSYS;
+
+  if(r._errno != 0)
+    errno = r._errno;
+
+  return -1;
+
+  /*r._errno = 0;
+
+  fs_path = fsdev_getfspath(&r, name);
+  if(r._errno != 0)
+    errno = r._errno;
+
+  if(fs_path.data == NULL)
+    return -1;*/
+
+  /*if(rc == 0)
+  {*/
+    /* convert from milliseconds to seconds */
+    //*mtime /= 1000;
+    /* convert from 2000-based timestamp to UNIX timestamp */
+    /**mtime += 946684800;
+  }
+
+  return rc;*/
+
+}
+
+/*! Error map */
+typedef struct
+{
+  Result fs_error; //!< Error from FS service
+  int    error;    //!< POSIX errno
+} error_map_t;
+
+/*! Error table */
+static const error_map_t error_table[] =
+{
+  /* keep this list sorted! */
+  { 0x202, ENOENT,       },
+  { 0x2EE202, EINVAL,       },
+  { 0x2EE602, ENAMETOOLONG, },
+};
+static const size_t num_errors = sizeof(error_table)/sizeof(error_table[0]);
+
+/*! Comparison function for bsearch on error_table
+ *
+ *  @param[in] p1 Left side of comparison
+ *  @param[in] p2 Right side of comparison
+ *
+ *  @returns <0 if lhs < rhs
+ *  @returns >0 if lhs > rhs
+ *  @returns 0  if lhs == rhs
+ */
+static int
+error_cmp(const void *p1, const void *p2)
+{
+  const error_map_t *lhs = (const error_map_t*)p1;
+  const error_map_t *rhs = (const error_map_t*)p2;
+
+  if((u32)lhs->fs_error < (u32)rhs->fs_error)
+    return -1;
+  else if((u32)lhs->fs_error > (u32)rhs->fs_error)
+    return 1;
+  return 0;
+}
+
+/*! Translate FS service error to errno
+ *
+ *  @param[in] error FS service error
+ *
+ *  @returns errno
+ */
+static int
+fsdev_translate_error(Result error)
+{
+  error_map_t key = { .fs_error = error };
+  const error_map_t *rc = bsearch(&key, error_table, num_errors,
+                                  sizeof(error_map_t), error_cmp);
+
+  if(rc != NULL)
+    return rc->error;
+
+  return (int)error;
+}
+
diff --git a/nx/source/services/fs.c b/nx/source/services/fs.c
index 8a84ceaa..907857b7 100644
--- a/nx/source/services/fs.c
+++ b/nx/source/services/fs.c
@@ -254,7 +254,7 @@ Result fsFsDeleteDirectoryRecursively(FsFileSystem* fs, const char* path) {
     return rc;
 }
 
-Result fsRenameFile(FsFileSystem* fs, const char* path0, const char* path1) {
+Result fsFsRenameFile(FsFileSystem* fs, const char* path0, const char* path1) {
     IpcCommand c;
     ipcInitialize(&c);
     ipcAddSendStatic(&c, path0, FS_MAX_PATH, 0);
@@ -287,7 +287,7 @@ Result fsRenameFile(FsFileSystem* fs, const char* path0, const char* path1) {
     return rc;
 }
 
-Result fsRenameDirectory(FsFileSystem* fs, const char* path0, const char* path1) {
+Result fsFsRenameDirectory(FsFileSystem* fs, const char* path0, const char* path1) {
     IpcCommand c;
     ipcInitialize(&c);
     ipcAddSendStatic(&c, path0, FS_MAX_PATH, 0);
@@ -584,7 +584,7 @@ Result fsFileRead(FsFile* f, u64 off, void* buf, size_t len, size_t* out) {
     return rc;
 }
 
-Result fsFileWrite(FsFile* f, u64 off, void* buf, size_t len, size_t* out) {
+Result fsFileWrite(FsFile* f, u64 off, const void* buf, size_t len, size_t* out) {
     IpcCommand c;
     ipcInitialize(&c);
     ipcAddSendBuffer(&c, buf, len, 1);
@@ -717,7 +717,8 @@ Result fsFileGetSize(FsFile* f, u64* out) {
             u64 size;
         } *resp = r.Raw;
 
-        rc = resp->size;
+        rc = resp->result;
+        if (R_SUCCEEDED(rc) && out) *out = resp->size;
     }
 
     return rc;
@@ -727,3 +728,80 @@ void fsFileClose(FsFile* f) {
     svcCloseHandle(f->h);
 }
 
+// IDirectory implementation
+void fsDirClose(FsDir* d) {
+    svcCloseHandle(d->h);
+}
+
+Result fsDirRead(FsDir* d, u64 inval, size_t* total_entries, size_t max_entries, FsDirectoryEntry *buf) {
+    IpcCommand c;
+    ipcInitialize(&c);
+    ipcAddRecvBuffer(&c, buf, sizeof(FsDirectoryEntry)*max_entries, 0);
+
+    struct {
+        u64 magic;
+        u64 cmd_id;
+        u64 inval;
+    } *raw;
+
+    raw = ipcPrepareHeader(&c, sizeof(*raw));
+
+    raw->magic = SFCI_MAGIC;
+    raw->cmd_id = 0;
+    raw->inval = inval;
+
+    Result rc = ipcDispatch(d->h);
+
+    if (R_SUCCEEDED(rc)) {
+        IpcCommandResponse r;
+        ipcParseResponse(&r);
+
+        struct {
+            u64 magic;
+            u64 result;
+            u64 total_entries;
+        } *resp = r.Raw;
+
+        rc = resp->result;
+
+        if (R_SUCCEEDED(rc)) {
+            if (total_entries) *total_entries = resp->total_entries;
+        }
+    }
+
+    return rc;
+}
+
+Result fsDirGetEntryCount(FsDir* d, u64* count) {
+    IpcCommand c;
+    ipcInitialize(&c);
+
+    struct {
+        u64 magic;
+        u64 cmd_id;
+    } *raw;
+
+    raw = ipcPrepareHeader(&c, sizeof(*raw));
+
+    raw->magic = SFCI_MAGIC;
+    raw->cmd_id = 1;
+
+    Result rc = ipcDispatch(d->h);
+
+    if (R_SUCCEEDED(rc)) {
+        IpcCommandResponse r;
+        ipcParseResponse(&r);
+
+        struct {
+            u64 magic;
+            u64 result;
+            u64 count;
+        } *resp = r.Raw;
+
+        rc = resp->result;
+        if (R_SUCCEEDED(rc) && count) *count = resp->count;
+    }
+
+    return rc;
+}
+
diff --git a/nx/source/system/init.c b/nx/source/system/init.c
index 717b7420..d3d1a57f 100644
--- a/nx/source/system/init.c
+++ b/nx/source/system/init.c
@@ -32,17 +32,21 @@ void __attribute__((weak)) __appInit(void)
 {
     // Initialize default services.
     smInitialize();
-    fsInitialize();
     appletInitialize();
     hidInitialize();
+
+    fsInitialize();
+    //fsdevInit();
 }
 
 void __attribute__((weak)) __appExit(void)
 {
     // Cleanup default services.
+    //fsdevExit();
+    fsExit();
+
     hidExit();
     appletExit();
-    fsExit();
     smExit();
 }
 
diff --git a/nx/source/util/utf/decode_utf16.c b/nx/source/util/utf/decode_utf16.c
new file mode 100644
index 00000000..086f10b8
--- /dev/null
+++ b/nx/source/util/utf/decode_utf16.c
@@ -0,0 +1,26 @@
+#include "switch/types.h"
+#include "switch/util/utf.h"
+
+ssize_t
+decode_utf16(uint32_t       *out,
+             const uint16_t *in)
+{
+  uint16_t code1, code2;
+
+  code1 = *in++;
+  if(code1 >= 0xD800 && code1 < 0xDC00)
+  {
+    /* surrogate pair */
+    code2 = *in++;
+    if(code2 >= 0xDC00 && code2 < 0xE000)
+    {
+      *out = (code1 << 10) + code2 - 0x35FDC00;
+      return 2;
+    }
+
+    return -1;
+  }
+
+  *out = code1;
+  return 1;
+}
diff --git a/nx/source/util/utf/decode_utf8.c b/nx/source/util/utf/decode_utf8.c
new file mode 100644
index 00000000..a4bd3f25
--- /dev/null
+++ b/nx/source/util/utf/decode_utf8.c
@@ -0,0 +1,88 @@
+#include "switch/util/utf.h"
+
+ssize_t
+decode_utf8(uint32_t      *out,
+            const uint8_t *in)
+{
+  uint8_t code1, code2, code3, code4;
+
+  code1 = *in++;
+  if(code1 < 0x80)
+  {
+    /* 1-byte sequence */
+    *out = code1;
+    return 1;
+  }
+  else if(code1 < 0xC2)
+  {
+    return -1;
+  }
+  else if(code1 < 0xE0)
+  {
+    /* 2-byte sequence */
+    code2 = *in++;
+    if((code2 & 0xC0) != 0x80)
+    {
+      return -1;
+    }
+
+    *out = (code1 << 6) + code2 - 0x3080;
+    return 2;
+  }
+  else if(code1 < 0xF0)
+  {
+    /* 3-byte sequence */
+    code2 = *in++;
+    if((code2 & 0xC0) != 0x80)
+    {
+      return -1;
+    }
+    if(code1 == 0xE0 && code2 < 0xA0)
+    {
+      return -1;
+    }
+
+    code3 = *in++;
+    if((code3 & 0xC0) != 0x80)
+    {
+      return -1;
+    }
+
+    *out = (code1 << 12) + (code2 << 6) + code3 - 0xE2080;
+    return 3;
+  }
+  else if(code1 < 0xF5)
+  {
+    /* 4-byte sequence */
+    code2 = *in++;
+    if((code2 & 0xC0) != 0x80)
+    {
+      return -1;
+    }
+    if(code1 == 0xF0 && code2 < 0x90)
+    {
+      return -1;
+    }
+    if(code1 == 0xF4 && code2 >= 0x90)
+    {
+      return -1;
+    }
+
+    code3 = *in++;
+    if((code3 & 0xC0) != 0x80)
+    {
+      return -1;
+    }
+
+    code4 = *in++;
+    if((code4 & 0xC0) != 0x80)
+    {
+      return -1;
+    }
+
+    *out = (code1 << 18) + (code2 << 12) + (code3 << 6) + code4 - 0x3C82080;
+    return 4;
+  }
+
+  return -1;
+}
diff --git a/nx/source/util/utf/encode_utf16.c b/nx/source/util/utf/encode_utf16.c
new file mode 100644
index 00000000..4a56fc9f
--- /dev/null
+++ b/nx/source/util/utf/encode_utf16.c
@@ -0,0 +1,24 @@
+#include "switch/util/utf.h"
+
+ssize_t
+encode_utf16(uint16_t *out,
+             uint32_t in)
+{
+  if(in < 0x10000)
+  {
+    if(out != NULL)
+      *out++ = in;
+    return 1;
+  }
+  else if(in < 0x110000)
+  {
+    if(out != NULL)
+    {
+      *out++ = (in >> 10) + 0xD7C0;
+      *out++ = (in & 0x3FF) + 0xDC00;
+    }
+    return 2;
+  }
+
+  return -1;
+}
diff --git a/nx/source/util/utf/encode_utf8.c b/nx/source/util/utf/encode_utf8.c
new file mode 100644
index 00000000..611606c7
--- /dev/null
+++ b/nx/source/util/utf/encode_utf8.c
@@ -0,0 +1,45 @@
+#include "switch/util/utf.h"
+
+ssize_t
+encode_utf8(uint8_t  *out,
+            uint32_t in)
+{
+  if(in < 0x80)
+  {
+    if(out != NULL)
+      *out++ = in;
+    return 1;
+  }
+  else if(in < 0x800)
+  {
+    if(out != NULL)
+    {
+      *out++ = (in >> 6) + 0xC0;
+      *out++ = (in & 0x3F) + 0x80;
+    }
+    return 2;
+  }
+  else if(in < 0x10000)
+  {
+    if(out != NULL)
+    {
+      *out++ = (in >> 12) + 0xE0;
+      *out++ = ((in >> 6) & 0x3F) + 0x80;
+      *out++ = (in & 0x3F) + 0x80;
+    }
+    return 3;
+  }
+  else if(in < 0x110000)
+  {
+    if(out != NULL)
+    {
+      *out++ = (in >> 18) + 0xF0;
+      *out++ = ((in >> 12) & 0x3F) + 0x80;
+      *out++ = ((in >> 6) & 0x3F) + 0x80;
+      *out++ = (in & 0x3F) + 0x80;
+    }
+    return 4;
+  }
+
+  return -1;
+}
diff --git a/nx/source/util/utf/utf16_to_utf32.c b/nx/source/util/utf/utf16_to_utf32.c
new file mode 100644
index 00000000..fd119842
--- /dev/null
+++ b/nx/source/util/utf/utf16_to_utf32.c
@@ -0,0 +1,37 @@
+#include "switch/types.h"
+#include "switch/util/utf.h"
+
+ssize_t
+utf16_to_utf32(uint32_t       *out,
+               const uint16_t *in,
+               size_t         len)
+{
+  ssize_t  rc = 0;
+  ssize_t  units;
+  uint32_t code;
+
+  do
+  {
+    units = decode_utf16(&code, in);
+    if(units == -1)
+      return -1;
+
+    if(code > 0)
+    {
+      in += units;
+
+      if(out != NULL)
+      {
+        if(rc < len)
+          *out++ = code;
+      }
+
+      if(SSIZE_MAX - 1 >= rc)
+        ++rc;
+      else
+        return -1;
+    }
+  } while(code > 0);
+
+  return rc;
+}
diff --git a/nx/source/util/utf/utf16_to_utf8.c b/nx/source/util/utf/utf16_to_utf8.c
new file mode 100644
index 00000000..187531f3
--- /dev/null
+++ b/nx/source/util/utf/utf16_to_utf8.c
@@ -0,0 +1,50 @@
+#include "switch/types.h"
+#include "switch/util/utf.h"
+
+ssize_t
+utf16_to_utf8(uint8_t        *out,
+              const uint16_t *in,
+              size_t         len)
+{
+  ssize_t  rc = 0;
+  ssize_t  units;
+  uint32_t code;
+  uint8_t  encoded[4];
+
+  do
+  {
+    units = decode_utf16(&code, in);
+    if(units == -1)
+      return -1;
+
+    if(code > 0)
+    {
+      in += units;
+
+      units = encode_utf8(encoded, code);
+      if(units == -1)
+        return -1;
+
+      if(out != NULL)
+      {
+        if(rc + units <= len)
+        {
+          *out++ = encoded[0];
+          if(units > 1)
+            *out++ = encoded[1];
+          if(units > 2)
+            *out++ = encoded[2];
+          if(units > 3)
+            *out++ = encoded[3];
+        }
+      }
+
+      if(SSIZE_MAX - units >= rc)
+        rc += units;
+      else
+        return -1;
+    }
+  } while(code > 0);
+
+  return rc;
+}
diff --git a/nx/source/util/utf/utf32_to_utf16.c b/nx/source/util/utf/utf32_to_utf16.c
new file mode 100644
index 00000000..d3385153
--- /dev/null
+++ b/nx/source/util/utf/utf32_to_utf16.c
@@ -0,0 +1,36 @@
+#include "switch/types.h"
+#include "switch/util/utf.h"
+
+ssize_t
+utf32_to_utf16(uint16_t       *out,
+               const uint32_t *in,
+               size_t         len)
+{
+  ssize_t  rc = 0;
+  ssize_t  units;
+  uint16_t encoded[2];
+
+  while(*in > 0)
+  {
+    units = encode_utf16(encoded, *in++);
+    if(units == -1)
+      return -1;
+
+    if(out != NULL)
+    {
+      if(rc + units <= len)
+      {
+        *out++ = encoded[0];
+        if(units > 1)
+          *out++ = encoded[1];
+      }
+    }
+
+    if(SSIZE_MAX - units >= rc)
+      rc += units;
+    else
+      return -1;
+  }
+
+  return rc;
+}
diff --git a/nx/source/util/utf/utf32_to_utf8.c b/nx/source/util/utf/utf32_to_utf8.c
new file mode 100644
index 00000000..f195765e
--- /dev/null
+++ b/nx/source/util/utf/utf32_to_utf8.c
@@ -0,0 +1,40 @@
+#include "switch/types.h"
+#include "switch/util/utf.h"
+
+ssize_t
+utf32_to_utf8(uint8_t        *out,
+              const uint32_t *in,
+              size_t         len)
+{
+  ssize_t  rc = 0;
+  ssize_t  units;
+  uint8_t  encoded[4];
+
+  while(*in > 0)
+  {
+    units = encode_utf8(encoded, *in++);
+    if(units == -1)
+      return -1;
+
+    if(out != NULL)
+    {
+      if(rc + units <= len)
+      {
+        *out++ = encoded[0];
+        if(units > 1)
+          *out++ = encoded[1];
+        if(units > 2)
+          *out++ = encoded[2];
+        if(units > 3)
+          *out++ = encoded[3];
+      }
+    }
+
+    if(SSIZE_MAX - units >= rc)
+      rc += units;
+    else
+      return -1;
+  }
+
+  return rc;
+}
diff --git a/nx/source/util/utf/utf8_to_utf16.c b/nx/source/util/utf/utf8_to_utf16.c
new file mode 100644
index 00000000..07878860
--- /dev/null
+++ b/nx/source/util/utf/utf8_to_utf16.c
@@ -0,0 +1,46 @@
+#include "switch/types.h"
+#include "switch/util/utf.h"
+
+ssize_t
+utf8_to_utf16(uint16_t      *out,
+              const uint8_t *in,
+              size_t        len)
+{
+  ssize_t  rc = 0;
+  ssize_t  units;
+  uint32_t code;
+  uint16_t encoded[2];
+
+  do
+  {
+    units = decode_utf8(&code, in);
+    if(units == -1)
+      return -1;
+
+    if(code > 0)
+    {
+      in += units;
+
+      units = encode_utf16(encoded, code);
+      if(units == -1)
+        return -1;
+
+      if(out != NULL)
+      {
+        if(rc + units <= len)
+        {
+          *out++ = encoded[0];
+          if(units > 1)
+            *out++ = encoded[1];
+        }
+      }
+
+      if(SSIZE_MAX - units >= rc)
+        rc += units;
+      else
+        return -1;
+    }
+  } while(code > 0);
+
+  return rc;
+}
diff --git a/nx/source/util/utf/utf8_to_utf32.c b/nx/source/util/utf/utf8_to_utf32.c
new file mode 100644
index 00000000..ada522e5
--- /dev/null
+++ b/nx/source/util/utf/utf8_to_utf32.c
@@ -0,0 +1,37 @@
+#include "switch/types.h"
+#include "switch/util/utf.h"
+
+ssize_t
+utf8_to_utf32(uint32_t      *out,
+              const uint8_t *in,
+              size_t        len)
+{
+  ssize_t  rc = 0;
+  ssize_t  units;
+  uint32_t code;
+
+  do
+  {
+    units = decode_utf8(&code, in);
+    if(units == -1)
+      return -1;
+
+    if(code > 0)
+    {
+      in += units;
+
+      if(out != NULL)
+      {
+        if(rc < len)
+          *out++ = code;
+      }
+
+      if(SSIZE_MAX - 1 >= rc)
+        ++rc;
+      else
+        return -1;
+    }
+  } while(code > 0);
+
+  return rc;
+}