diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 4a9c6fd2fe..299f1f261e 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -17,13 +17,16 @@ set(SRCS
             core_timing.cpp
             file_sys/archive_backend.cpp
             file_sys/archive_extsavedata.cpp
+            file_sys/archive_ncch.cpp
             file_sys/archive_romfs.cpp
             file_sys/archive_savedata.cpp
-            file_sys/archive_savedatacheck.cpp
             file_sys/archive_sdmc.cpp
+            file_sys/archive_sdmcwriteonly.cpp
             file_sys/archive_systemsavedata.cpp
             file_sys/disk_archive.cpp
             file_sys/ivfc_archive.cpp
+            file_sys/path_parser.cpp
+            file_sys/savedata_archive.cpp
             gdbstub/gdbstub.cpp
             hle/config_mem.cpp
             hle/hle.cpp
@@ -159,15 +162,18 @@ set(HEADERS
             core_timing.h
             file_sys/archive_backend.h
             file_sys/archive_extsavedata.h
+            file_sys/archive_ncch.h
             file_sys/archive_romfs.h
             file_sys/archive_savedata.h
-            file_sys/archive_savedatacheck.h
             file_sys/archive_sdmc.h
+            file_sys/archive_sdmcwriteonly.h
             file_sys/archive_systemsavedata.h
             file_sys/directory_backend.h
             file_sys/disk_archive.h
             file_sys/file_backend.h
             file_sys/ivfc_archive.h
+            file_sys/path_parser.h
+            file_sys/savedata_archive.h
             gdbstub/gdbstub.h
             hle/config_mem.h
             hle/function_wrappers.h
diff --git a/src/core/file_sys/archive_backend.h b/src/core/file_sys/archive_backend.h
index 06b8f2ed77..58f6c150c6 100644
--- a/src/core/file_sys/archive_backend.h
+++ b/src/core/file_sys/archive_backend.h
@@ -87,7 +87,7 @@ public:
      * @return Opened file, or error code
      */
     virtual ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
-                                                             const Mode mode) const = 0;
+                                                             const Mode& mode) const = 0;
 
     /**
      * Delete a file specified by its path
@@ -100,53 +100,53 @@ public:
      * Rename a File specified by its path
      * @param src_path Source path relative to the archive
      * @param dest_path Destination path relative to the archive
-     * @return Whether rename succeeded
+     * @return Result of the operation
      */
-    virtual bool RenameFile(const Path& src_path, const Path& dest_path) const = 0;
+    virtual ResultCode RenameFile(const Path& src_path, const Path& dest_path) const = 0;
 
     /**
      * Delete a directory specified by its path
      * @param path Path relative to the archive
-     * @return Whether the directory could be deleted
+     * @return Result of the operation
      */
-    virtual bool DeleteDirectory(const Path& path) const = 0;
+    virtual ResultCode DeleteDirectory(const Path& path) const = 0;
 
     /**
      * Delete a directory specified by its path and anything under it
      * @param path Path relative to the archive
-     * @return Whether the directory could be deleted
+     * @return Result of the operation
      */
-    virtual bool DeleteDirectoryRecursively(const Path& path) const = 0;
+    virtual ResultCode DeleteDirectoryRecursively(const Path& path) const = 0;
 
     /**
      * Create a file specified by its path
      * @param path Path relative to the Archive
      * @param size The size of the new file, filled with zeroes
-     * @return File creation result code
+     * @return Result of the operation
      */
     virtual ResultCode CreateFile(const Path& path, u64 size) const = 0;
 
     /**
      * Create a directory specified by its path
      * @param path Path relative to the archive
-     * @return Whether the directory could be created
+     * @return Result of the operation
      */
-    virtual bool CreateDirectory(const Path& path) const = 0;
+    virtual ResultCode CreateDirectory(const Path& path) const = 0;
 
     /**
      * Rename a Directory specified by its path
      * @param src_path Source path relative to the archive
      * @param dest_path Destination path relative to the archive
-     * @return Whether rename succeeded
+     * @return Result of the operation
      */
-    virtual bool RenameDirectory(const Path& src_path, const Path& dest_path) const = 0;
+    virtual ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const = 0;
 
     /**
      * Open a directory specified by its path
      * @param path Path relative to the archive
-     * @return Opened directory, or nullptr
+     * @return Opened directory, or error code
      */
-    virtual std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const = 0;
+    virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const = 0;
 
     /**
      * Get the free space
diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp
index e1d29efd3d..e1c4931ecc 100644
--- a/src/core/file_sys/archive_extsavedata.cpp
+++ b/src/core/file_sys/archive_extsavedata.cpp
@@ -11,6 +11,9 @@
 #include "common/string_util.h"
 #include "core/file_sys/archive_extsavedata.h"
 #include "core/file_sys/disk_archive.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/path_parser.h"
+#include "core/file_sys/savedata_archive.h"
 #include "core/hle/service/fs/archive.h"
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -18,6 +21,116 @@
 
 namespace FileSys {
 
+/**
+ * A modified version of DiskFile for fixed-size file used by ExtSaveData
+ * The file size can't be changed by SetSize or Write.
+ */
+class FixSizeDiskFile : public DiskFile {
+public:
+    FixSizeDiskFile(FileUtil::IOFile&& file, const Mode& mode) : DiskFile(std::move(file), mode) {
+        size = GetSize();
+    }
+
+    bool SetSize(u64 size) const override {
+        return false;
+    }
+
+    ResultVal<size_t> Write(u64 offset, size_t length, bool flush,
+                            const u8* buffer) const override {
+        if (offset > size) {
+            return ResultCode(ErrorDescription::FS_WriteBeyondEnd, ErrorModule::FS,
+                              ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+        } else if (offset == size) {
+            return MakeResult<size_t>(0);
+        }
+
+        if (offset + length > size) {
+            length = size - offset;
+        }
+
+        return DiskFile::Write(offset, length, flush, buffer);
+    }
+
+private:
+    u64 size{};
+};
+
+/**
+ * Archive backend for general extsave data archive type.
+ * The behaviour of ExtSaveDataArchive is almost the same as SaveDataArchive, except for
+ *  - file size can't be changed once created (thus creating zero-size file and openning with create
+ *    flag are prohibited);
+ *  - always open a file with read+write permission.
+ */
+class ExtSaveDataArchive : public SaveDataArchive {
+public:
+    ExtSaveDataArchive(const std::string& mount_point) : SaveDataArchive(mount_point) {}
+
+    std::string GetName() const override {
+        return "ExtSaveDataArchive: " + mount_point;
+    }
+
+    ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
+                                                     const Mode& mode) const override {
+        LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
+
+        const PathParser path_parser(path);
+
+        if (!path_parser.IsValid()) {
+            LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+            return ERROR_INVALID_PATH;
+        }
+
+        if (mode.hex == 0) {
+            LOG_ERROR(Service_FS, "Empty open mode");
+            return ERROR_UNSUPPORTED_OPEN_FLAGS;
+        }
+
+        if (mode.create_flag) {
+            LOG_ERROR(Service_FS, "Create flag is not supported");
+            return ERROR_UNSUPPORTED_OPEN_FLAGS;
+        }
+
+        const auto full_path = path_parser.BuildHostPath(mount_point);
+
+        switch (path_parser.GetHostStatus(mount_point)) {
+        case PathParser::InvalidMountPoint:
+            LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+            return ERROR_FILE_NOT_FOUND;
+        case PathParser::PathNotFound:
+            LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+            return ERROR_PATH_NOT_FOUND;
+        case PathParser::FileInPath:
+        case PathParser::DirectoryFound:
+            LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str());
+            return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+        case PathParser::NotFound:
+            LOG_ERROR(Service_FS, "%s not found", full_path.c_str());
+            return ERROR_FILE_NOT_FOUND;
+        }
+
+        FileUtil::IOFile file(full_path, "r+b");
+        if (!file.IsOpen()) {
+            LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str());
+            return ERROR_FILE_NOT_FOUND;
+        }
+
+        Mode rwmode;
+        rwmode.write_flag.Assign(1);
+        rwmode.read_flag.Assign(1);
+        auto disk_file = std::make_unique<FixSizeDiskFile>(std::move(file), rwmode);
+        return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file));
+    }
+
+    ResultCode CreateFile(const Path& path, u64 size) const override {
+        if (size == 0) {
+            LOG_ERROR(Service_FS, "Zero-size file is not supported");
+            return ERROR_UNSUPPORTED_OPEN_FLAGS;
+        }
+        return SaveDataArchive::CreateFile(path, size);
+    }
+};
+
 std::string GetExtSaveDataPath(const std::string& mount_point, const Path& path) {
     std::vector<u8> vec_data = path.AsBinary();
     const u32* data = reinterpret_cast<const u32*>(vec_data.data());
@@ -84,7 +197,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(cons
                               ErrorSummary::InvalidState, ErrorLevel::Status);
         }
     }
-    auto archive = std::make_unique<DiskArchive>(fullpath);
+    auto archive = std::make_unique<ExtSaveDataArchive>(fullpath);
     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
 }
 
diff --git a/src/core/file_sys/archive_savedatacheck.cpp b/src/core/file_sys/archive_ncch.cpp
similarity index 63%
rename from src/core/file_sys/archive_savedatacheck.cpp
rename to src/core/file_sys/archive_ncch.cpp
index 6c4542b7d2..6f1aadfc3d 100644
--- a/src/core/file_sys/archive_savedatacheck.cpp
+++ b/src/core/file_sys/archive_ncch.cpp
@@ -9,7 +9,7 @@
 #include "common/file_util.h"
 #include "common/logging/log.h"
 #include "common/string_util.h"
-#include "core/file_sys/archive_savedatacheck.h"
+#include "core/file_sys/archive_ncch.h"
 #include "core/file_sys/ivfc_archive.h"
 #include "core/hle/service/fs/archive.h"
 
@@ -18,22 +18,22 @@
 
 namespace FileSys {
 
-static std::string GetSaveDataCheckContainerPath(const std::string& nand_directory) {
+static std::string GetNCCHContainerPath(const std::string& nand_directory) {
     return Common::StringFromFormat("%s%s/title/", nand_directory.c_str(), SYSTEM_ID.c_str());
 }
 
-static std::string GetSaveDataCheckPath(const std::string& mount_point, u32 high, u32 low) {
+static std::string GetNCCHPath(const std::string& mount_point, u32 high, u32 low) {
     return Common::StringFromFormat("%s%08x/%08x/content/00000000.app.romfs", mount_point.c_str(),
                                     high, low);
 }
 
-ArchiveFactory_SaveDataCheck::ArchiveFactory_SaveDataCheck(const std::string& nand_directory)
-    : mount_point(GetSaveDataCheckContainerPath(nand_directory)) {}
+ArchiveFactory_NCCH::ArchiveFactory_NCCH(const std::string& nand_directory)
+    : mount_point(GetNCCHContainerPath(nand_directory)) {}
 
-ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveDataCheck::Open(const Path& path) {
+ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path& path) {
     auto vec = path.AsBinary();
     const u32* data = reinterpret_cast<u32*>(vec.data());
-    std::string file_path = GetSaveDataCheckPath(mount_point, data[1], data[0]);
+    std::string file_path = GetNCCHPath(mount_point, data[1], data[0]);
     auto file = std::make_shared<FileUtil::IOFile>(file_path, "rb");
 
     if (!file->IsOpen()) {
@@ -45,15 +45,15 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveDataCheck::Open(co
     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
 }
 
-ResultCode ArchiveFactory_SaveDataCheck::Format(const Path& path,
-                                                const FileSys::ArchiveFormatInfo& format_info) {
-    LOG_ERROR(Service_FS, "Attempted to format a SaveDataCheck archive.");
+ResultCode ArchiveFactory_NCCH::Format(const Path& path,
+                                       const FileSys::ArchiveFormatInfo& format_info) {
+    LOG_ERROR(Service_FS, "Attempted to format a NCCH archive.");
     // TODO: Verify error code
     return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported,
                       ErrorLevel::Permanent);
 }
 
-ResultVal<ArchiveFormatInfo> ArchiveFactory_SaveDataCheck::GetFormatInfo(const Path& path) const {
+ResultVal<ArchiveFormatInfo> ArchiveFactory_NCCH::GetFormatInfo(const Path& path) const {
     // TODO(Subv): Implement
     LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
     return ResultCode(-1);
diff --git a/src/core/file_sys/archive_savedatacheck.h b/src/core/file_sys/archive_ncch.h
similarity index 78%
rename from src/core/file_sys/archive_savedatacheck.h
rename to src/core/file_sys/archive_ncch.h
index e9cafbed9f..66b8ce75d0 100644
--- a/src/core/file_sys/archive_savedatacheck.h
+++ b/src/core/file_sys/archive_ncch.h
@@ -14,13 +14,13 @@
 
 namespace FileSys {
 
-/// File system interface to the SaveDataCheck archive
-class ArchiveFactory_SaveDataCheck final : public ArchiveFactory {
+/// File system interface to the NCCH archive
+class ArchiveFactory_NCCH final : public ArchiveFactory {
 public:
-    ArchiveFactory_SaveDataCheck(const std::string& mount_point);
+    ArchiveFactory_NCCH(const std::string& mount_point);
 
     std::string GetName() const override {
-        return "SaveDataCheck";
+        return "NCCH";
     }
 
     ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override;
diff --git a/src/core/file_sys/archive_savedata.cpp b/src/core/file_sys/archive_savedata.cpp
index 6711035ece..ecb44a215d 100644
--- a/src/core/file_sys/archive_savedata.cpp
+++ b/src/core/file_sys/archive_savedata.cpp
@@ -9,7 +9,7 @@
 #include "common/logging/log.h"
 #include "common/string_util.h"
 #include "core/file_sys/archive_savedata.h"
-#include "core/file_sys/disk_archive.h"
+#include "core/file_sys/savedata_archive.h"
 #include "core/hle/kernel/process.h"
 #include "core/hle/service/fs/archive.h"
 
@@ -54,7 +54,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveData::Open(const P
                           ErrorSummary::InvalidState, ErrorLevel::Status);
     }
 
-    auto archive = std::make_unique<DiskArchive>(std::move(concrete_mount_point));
+    auto archive = std::make_unique<SaveDataArchive>(std::move(concrete_mount_point));
     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
 }
 
diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp
index bcb03ed36e..333dfb92ed 100644
--- a/src/core/file_sys/archive_sdmc.cpp
+++ b/src/core/file_sys/archive_sdmc.cpp
@@ -8,6 +8,8 @@
 #include "common/logging/log.h"
 #include "core/file_sys/archive_sdmc.h"
 #include "core/file_sys/disk_archive.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/path_parser.h"
 #include "core/settings.h"
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -15,6 +17,281 @@
 
 namespace FileSys {
 
+ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFile(const Path& path,
+                                                              const Mode& mode) const {
+    Mode modified_mode;
+    modified_mode.hex = mode.hex;
+
+    // SDMC archive always opens a file with at least read permission
+    modified_mode.read_flag.Assign(1);
+
+    return OpenFileBase(path, modified_mode);
+}
+
+ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFileBase(const Path& path,
+                                                                  const Mode& mode) const {
+    LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
+
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    if (mode.hex == 0) {
+        LOG_ERROR(Service_FS, "Empty open mode");
+        return ERROR_INVALID_OPEN_FLAGS;
+    }
+
+    if (mode.create_flag && !mode.write_flag) {
+        LOG_ERROR(Service_FS, "Create flag set but write flag not set");
+        return ERROR_INVALID_OPEN_FLAGS;
+    }
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_NOT_FOUND;
+    case PathParser::PathNotFound:
+    case PathParser::FileInPath:
+        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+        return ERROR_NOT_FOUND;
+    case PathParser::DirectoryFound:
+        LOG_ERROR(Service_FS, "%s is not a file", full_path.c_str());
+        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+    case PathParser::NotFound:
+        if (!mode.create_flag) {
+            LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.",
+                      full_path.c_str());
+            return ERROR_NOT_FOUND;
+        } else {
+            // Create the file
+            FileUtil::CreateEmptyFile(full_path);
+        }
+        break;
+    }
+
+    FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
+    if (!file.IsOpen()) {
+        LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str());
+        return ERROR_NOT_FOUND;
+    }
+
+    auto disk_file = std::make_unique<DiskFile>(std::move(file), mode);
+    return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file));
+}
+
+ResultCode SDMCArchive::DeleteFile(const Path& path) const {
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_NOT_FOUND;
+    case PathParser::PathNotFound:
+    case PathParser::FileInPath:
+    case PathParser::NotFound:
+        LOG_ERROR(Service_FS, "%s not found", full_path.c_str());
+        return ERROR_NOT_FOUND;
+    case PathParser::DirectoryFound:
+        LOG_ERROR(Service_FS, "%s is not a file", full_path.c_str());
+        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+    }
+
+    if (FileUtil::Delete(full_path)) {
+        return RESULT_SUCCESS;
+    }
+
+    LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str());
+    return ERROR_NOT_FOUND;
+}
+
+ResultCode SDMCArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
+    if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) {
+        return RESULT_SUCCESS;
+    }
+
+    // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
+    // exist or similar. Verify.
+    return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
+                      ErrorSummary::NothingHappened, ErrorLevel::Status);
+}
+
+template <typename T>
+static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point,
+                                        T deleter) {
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    if (path_parser.IsRootDirectory())
+        return ERROR_NOT_FOUND;
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_NOT_FOUND;
+    case PathParser::PathNotFound:
+    case PathParser::NotFound:
+        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+        return ERROR_NOT_FOUND;
+    case PathParser::FileInPath:
+    case PathParser::FileFound:
+        LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
+        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+    }
+
+    if (deleter(full_path)) {
+        return RESULT_SUCCESS;
+    }
+
+    LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str());
+    return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+}
+
+ResultCode SDMCArchive::DeleteDirectory(const Path& path) const {
+    return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir);
+}
+
+ResultCode SDMCArchive::DeleteDirectoryRecursively(const Path& path) const {
+    return DeleteDirectoryHelper(
+        path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
+}
+
+ResultCode SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const {
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_NOT_FOUND;
+    case PathParser::PathNotFound:
+    case PathParser::FileInPath:
+        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+        return ERROR_NOT_FOUND;
+    case PathParser::DirectoryFound:
+        LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
+        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+    case PathParser::FileFound:
+        LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
+        return ERROR_ALREADY_EXISTS;
+    }
+
+    if (size == 0) {
+        FileUtil::CreateEmptyFile(full_path);
+        return RESULT_SUCCESS;
+    }
+
+    FileUtil::IOFile file(full_path, "wb");
+    // Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
+    // We do this by seeking to the right size, then writing a single null byte.
+    if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
+        return RESULT_SUCCESS;
+    }
+
+    LOG_ERROR(Service_FS, "Too large file");
+    return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource,
+                      ErrorLevel::Info);
+}
+
+ResultCode SDMCArchive::CreateDirectory(const Path& path) const {
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_NOT_FOUND;
+    case PathParser::PathNotFound:
+    case PathParser::FileInPath:
+        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+        return ERROR_NOT_FOUND;
+    case PathParser::DirectoryFound:
+    case PathParser::FileFound:
+        LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
+        return ERROR_ALREADY_EXISTS;
+    }
+
+    if (FileUtil::CreateDir(mount_point + path.AsString())) {
+        return RESULT_SUCCESS;
+    }
+
+    LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str());
+    return ResultCode(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled,
+                      ErrorLevel::Status);
+}
+
+ResultCode SDMCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
+    if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString()))
+        return RESULT_SUCCESS;
+
+    // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
+    // exist or similar. Verify.
+    return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
+                      ErrorSummary::NothingHappened, ErrorLevel::Status);
+}
+
+ResultVal<std::unique_ptr<DirectoryBackend>> SDMCArchive::OpenDirectory(const Path& path) const {
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_NOT_FOUND;
+    case PathParser::PathNotFound:
+    case PathParser::NotFound:
+    case PathParser::FileFound:
+        LOG_ERROR(Service_FS, "%s not found", full_path.c_str());
+        return ERROR_NOT_FOUND;
+    case PathParser::FileInPath:
+        LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
+        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+    }
+
+    auto directory = std::make_unique<DiskDirectory>(full_path);
+    return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
+}
+
+u64 SDMCArchive::GetFreeBytes() const {
+    // TODO: Stubbed to return 1GiB
+    return 1024 * 1024 * 1024;
+}
+
 ArchiveFactory_SDMC::ArchiveFactory_SDMC(const std::string& sdmc_directory)
     : sdmc_directory(sdmc_directory) {
     LOG_INFO(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str());
@@ -35,7 +312,7 @@ bool ArchiveFactory_SDMC::Initialize() {
 }
 
 ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMC::Open(const Path& path) {
-    auto archive = std::make_unique<DiskArchive>(sdmc_directory);
+    auto archive = std::make_unique<SDMCArchive>(sdmc_directory);
     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
 }
 
diff --git a/src/core/file_sys/archive_sdmc.h b/src/core/file_sys/archive_sdmc.h
index 88e855351d..9d99b110cb 100644
--- a/src/core/file_sys/archive_sdmc.h
+++ b/src/core/file_sys/archive_sdmc.h
@@ -14,6 +14,32 @@
 
 namespace FileSys {
 
+/// Archive backend for SDMC archive
+class SDMCArchive : public ArchiveBackend {
+public:
+    SDMCArchive(const std::string& mount_point_) : mount_point(mount_point_) {}
+
+    std::string GetName() const override {
+        return "SDMCArchive: " + mount_point;
+    }
+
+    ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
+                                                     const Mode& mode) const override;
+    ResultCode DeleteFile(const Path& path) const override;
+    ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
+    ResultCode DeleteDirectory(const Path& path) const override;
+    ResultCode DeleteDirectoryRecursively(const Path& path) const override;
+    ResultCode CreateFile(const Path& path, u64 size) const override;
+    ResultCode CreateDirectory(const Path& path) const override;
+    ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
+    ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
+    u64 GetFreeBytes() const override;
+
+protected:
+    ResultVal<std::unique_ptr<FileBackend>> OpenFileBase(const Path& path, const Mode& mode) const;
+    std::string mount_point;
+};
+
 /// File system interface to the SDMC archive
 class ArchiveFactory_SDMC final : public ArchiveFactory {
 public:
diff --git a/src/core/file_sys/archive_sdmcwriteonly.cpp b/src/core/file_sys/archive_sdmcwriteonly.cpp
new file mode 100644
index 0000000000..2aafc9b1d0
--- /dev/null
+++ b/src/core/file_sys/archive_sdmcwriteonly.cpp
@@ -0,0 +1,70 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include "common/file_util.h"
+#include "core/file_sys/archive_sdmcwriteonly.h"
+#include "core/file_sys/directory_backend.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/file_backend.h"
+#include "core/settings.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+ResultVal<std::unique_ptr<FileBackend>> SDMCWriteOnlyArchive::OpenFile(const Path& path,
+                                                                       const Mode& mode) const {
+    if (mode.read_flag) {
+        LOG_ERROR(Service_FS, "Read flag is not supported");
+        return ERROR_INVALID_READ_FLAG;
+    }
+    return SDMCArchive::OpenFileBase(path, mode);
+}
+
+ResultVal<std::unique_ptr<DirectoryBackend>> SDMCWriteOnlyArchive::OpenDirectory(
+    const Path& path) const {
+    LOG_ERROR(Service_FS, "Not supported");
+    return ERROR_UNSUPPORTED_OPEN_FLAGS;
+}
+
+ArchiveFactory_SDMCWriteOnly::ArchiveFactory_SDMCWriteOnly(const std::string& mount_point)
+    : sdmc_directory(mount_point) {
+    LOG_INFO(Service_FS, "Directory %s set as SDMCWriteOnly.", sdmc_directory.c_str());
+}
+
+bool ArchiveFactory_SDMCWriteOnly::Initialize() {
+    if (!Settings::values.use_virtual_sd) {
+        LOG_WARNING(Service_FS, "SDMC disabled by config.");
+        return false;
+    }
+
+    if (!FileUtil::CreateFullPath(sdmc_directory)) {
+        LOG_ERROR(Service_FS, "Unable to create SDMC path.");
+        return false;
+    }
+
+    return true;
+}
+
+ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMCWriteOnly::Open(const Path& path) {
+    auto archive = std::make_unique<SDMCWriteOnlyArchive>(sdmc_directory);
+    return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
+}
+
+ResultCode ArchiveFactory_SDMCWriteOnly::Format(const Path& path,
+                                                const FileSys::ArchiveFormatInfo& format_info) {
+    // TODO(wwylele): hwtest this
+    LOG_ERROR(Service_FS, "Attempted to format a SDMC write-only archive.");
+    return ResultCode(-1);
+}
+
+ResultVal<ArchiveFormatInfo> ArchiveFactory_SDMCWriteOnly::GetFormatInfo(const Path& path) const {
+    // TODO(Subv): Implement
+    LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
+    return ResultCode(-1);
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/archive_sdmcwriteonly.h b/src/core/file_sys/archive_sdmcwriteonly.h
new file mode 100644
index 0000000000..ed977485ad
--- /dev/null
+++ b/src/core/file_sys/archive_sdmcwriteonly.h
@@ -0,0 +1,57 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/file_sys/archive_sdmc.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+/**
+ * Archive backend for SDMC write-only archive.
+ * The behaviour of SDMCWriteOnlyArchive is almost the same as SDMCArchive, except for
+ *  - OpenDirectory is unsupported;
+ *  - OpenFile with read flag is unsupported.
+ */
+class SDMCWriteOnlyArchive : public SDMCArchive {
+public:
+    SDMCWriteOnlyArchive(const std::string& mount_point) : SDMCArchive(mount_point) {}
+
+    std::string GetName() const override {
+        return "SDMCWriteOnlyArchive: " + mount_point;
+    }
+
+    ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
+                                                     const Mode& mode) const override;
+
+    ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
+};
+
+/// File system interface to the SDMC write-only archive
+class ArchiveFactory_SDMCWriteOnly final : public ArchiveFactory {
+public:
+    ArchiveFactory_SDMCWriteOnly(const std::string& mount_point);
+
+    /**
+     * Initialize the archive.
+     * @return true if it initialized successfully
+     */
+    bool Initialize();
+
+    std::string GetName() const override {
+        return "SDMCWriteOnly";
+    }
+
+    ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override;
+    ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) override;
+    ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
+
+private:
+    std::string sdmc_directory;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/archive_systemsavedata.cpp b/src/core/file_sys/archive_systemsavedata.cpp
index 48ebc0ed49..54e7793e09 100644
--- a/src/core/file_sys/archive_systemsavedata.cpp
+++ b/src/core/file_sys/archive_systemsavedata.cpp
@@ -9,7 +9,7 @@
 #include "common/file_util.h"
 #include "common/string_util.h"
 #include "core/file_sys/archive_systemsavedata.h"
-#include "core/file_sys/disk_archive.h"
+#include "core/file_sys/savedata_archive.h"
 #include "core/hle/service/fs/archive.h"
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -56,7 +56,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(c
         return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS,
                           ErrorSummary::InvalidState, ErrorLevel::Status);
     }
-    auto archive = std::make_unique<DiskArchive>(fullpath);
+    auto archive = std::make_unique<SaveDataArchive>(fullpath);
     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
 }
 
diff --git a/src/core/file_sys/directory_backend.h b/src/core/file_sys/directory_backend.h
index b55e382eff..0c93f20746 100644
--- a/src/core/file_sys/directory_backend.h
+++ b/src/core/file_sys/directory_backend.h
@@ -40,12 +40,6 @@ public:
     DirectoryBackend() {}
     virtual ~DirectoryBackend() {}
 
-    /**
-    * Open the directory
-    * @return true if the directory opened correctly
-    */
-    virtual bool Open() = 0;
-
     /**
      * List files contained in the directory
      * @param count Number of entries to return at once in entries
diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp
index 2f05af3616..a243d9a137 100644
--- a/src/core/file_sys/disk_archive.cpp
+++ b/src/core/file_sys/disk_archive.cpp
@@ -15,144 +15,8 @@
 
 namespace FileSys {
 
-ResultVal<std::unique_ptr<FileBackend>> DiskArchive::OpenFile(const Path& path,
-                                                              const Mode mode) const {
-    LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
-    auto file = std::make_unique<DiskFile>(*this, path, mode);
-    ResultCode result = file->Open();
-    if (result.IsError())
-        return result;
-    return MakeResult<std::unique_ptr<FileBackend>>(std::move(file));
-}
-
-ResultCode DiskArchive::DeleteFile(const Path& path) const {
-    std::string file_path = mount_point + path.AsString();
-
-    if (FileUtil::IsDirectory(file_path))
-        return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled,
-                          ErrorLevel::Status);
-
-    if (!FileUtil::Exists(file_path))
-        return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound,
-                          ErrorLevel::Status);
-
-    if (FileUtil::Delete(file_path))
-        return RESULT_SUCCESS;
-
-    return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled,
-                      ErrorLevel::Status);
-}
-
-bool DiskArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
-    return FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString());
-}
-
-bool DiskArchive::DeleteDirectory(const Path& path) const {
-    return FileUtil::DeleteDir(mount_point + path.AsString());
-}
-
-bool DiskArchive::DeleteDirectoryRecursively(const Path& path) const {
-    return FileUtil::DeleteDirRecursively(mount_point + path.AsString());
-}
-
-ResultCode DiskArchive::CreateFile(const FileSys::Path& path, u64 size) const {
-    std::string full_path = mount_point + path.AsString();
-
-    if (FileUtil::IsDirectory(full_path))
-        return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled,
-                          ErrorLevel::Status);
-
-    if (FileUtil::Exists(full_path))
-        return ResultCode(ErrorDescription::FS_AlreadyExists, ErrorModule::FS,
-                          ErrorSummary::NothingHappened, ErrorLevel::Status);
-
-    if (size == 0) {
-        FileUtil::CreateEmptyFile(full_path);
-        return RESULT_SUCCESS;
-    }
-
-    FileUtil::IOFile file(full_path, "wb");
-    // Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
-    // We do this by seeking to the right size, then writing a single null byte.
-    if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1)
-        return RESULT_SUCCESS;
-
-    return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource,
-                      ErrorLevel::Info);
-}
-
-bool DiskArchive::CreateDirectory(const Path& path) const {
-    return FileUtil::CreateDir(mount_point + path.AsString());
-}
-
-bool DiskArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
-    return FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString());
-}
-
-std::unique_ptr<DirectoryBackend> DiskArchive::OpenDirectory(const Path& path) const {
-    LOG_DEBUG(Service_FS, "called path=%s", path.DebugStr().c_str());
-    auto directory = std::make_unique<DiskDirectory>(*this, path);
-    if (!directory->Open())
-        return nullptr;
-    return std::move(directory);
-}
-
-u64 DiskArchive::GetFreeBytes() const {
-    // TODO: Stubbed to return 1GiB
-    return 1024 * 1024 * 1024;
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-
-DiskFile::DiskFile(const DiskArchive& archive, const Path& path, const Mode mode) {
-    // TODO(Link Mauve): normalize path into an absolute path without "..", it can currently bypass
-    // the root directory we set while opening the archive.
-    // For example, opening /../../etc/passwd can give the emulated program your users list.
-    this->path = archive.mount_point + path.AsString();
-    this->mode.hex = mode.hex;
-}
-
-ResultCode DiskFile::Open() {
-    if (FileUtil::IsDirectory(path))
-        return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled,
-                          ErrorLevel::Status);
-
-    // Specifying only the Create flag is invalid
-    if (mode.create_flag && !mode.read_flag && !mode.write_flag) {
-        return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS,
-                          ErrorSummary::Canceled, ErrorLevel::Status);
-    }
-
-    if (!FileUtil::Exists(path)) {
-        if (!mode.create_flag) {
-            LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.",
-                      path.c_str());
-            return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS,
-                              ErrorSummary::NotFound, ErrorLevel::Status);
-        } else {
-            // Create the file
-            FileUtil::CreateEmptyFile(path);
-        }
-    }
-
-    std::string mode_string = "";
-    if (mode.write_flag)
-        mode_string += "r+"; // Files opened with Write access can be read from
-    else if (mode.read_flag)
-        mode_string += "r";
-
-    // Open the file in binary mode, to avoid problems with CR/LF on Windows systems
-    mode_string += "b";
-
-    file = std::make_unique<FileUtil::IOFile>(path, mode_string.c_str());
-    if (file->IsOpen())
-        return RESULT_SUCCESS;
-    return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound,
-                      ErrorLevel::Status);
-}
-
 ResultVal<size_t> DiskFile::Read(const u64 offset, const size_t length, u8* buffer) const {
-    if (!mode.read_flag && !mode.write_flag)
+    if (!mode.read_flag)
         return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS,
                           ErrorSummary::Canceled, ErrorLevel::Status);
 
@@ -189,21 +53,11 @@ bool DiskFile::Close() const {
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 
-DiskDirectory::DiskDirectory(const DiskArchive& archive, const Path& path) : directory() {
-    // TODO(Link Mauve): normalize path into an absolute path without "..", it can currently bypass
-    // the root directory we set while opening the archive.
-    // For example, opening /../../usr/bin can give the emulated program your installed programs.
-    this->path = archive.mount_point + path.AsString();
-}
-
-bool DiskDirectory::Open() {
-    if (!FileUtil::IsDirectory(path))
-        return false;
+DiskDirectory::DiskDirectory(const std::string& path) : directory() {
     unsigned size = FileUtil::ScanDirectoryTree(path, directory);
     directory.size = size;
     directory.isDirectory = true;
     children_iterator = directory.children.begin();
-    return true;
 }
 
 u32 DiskDirectory::Read(const u32 count, Entry* entries) {
diff --git a/src/core/file_sys/disk_archive.h b/src/core/file_sys/disk_archive.h
index 59ebb20021..eb9166df6d 100644
--- a/src/core/file_sys/disk_archive.h
+++ b/src/core/file_sys/disk_archive.h
@@ -20,43 +20,13 @@
 
 namespace FileSys {
 
-/**
- * Helper which implements a backend accessing the host machine's filesystem.
- * This should be subclassed by concrete archive types, which will provide the
- * base directory on the host filesystem and override any required functionality.
- */
-class DiskArchive : public ArchiveBackend {
-public:
-    DiskArchive(const std::string& mount_point_) : mount_point(mount_point_) {}
-
-    virtual std::string GetName() const override {
-        return "DiskArchive: " + mount_point;
-    }
-
-    ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
-                                                     const Mode mode) const override;
-    ResultCode DeleteFile(const Path& path) const override;
-    bool RenameFile(const Path& src_path, const Path& dest_path) const override;
-    bool DeleteDirectory(const Path& path) const override;
-    bool DeleteDirectoryRecursively(const Path& path) const override;
-    ResultCode CreateFile(const Path& path, u64 size) const override;
-    bool CreateDirectory(const Path& path) const override;
-    bool RenameDirectory(const Path& src_path, const Path& dest_path) const override;
-    std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const override;
-    u64 GetFreeBytes() const override;
-
-protected:
-    friend class DiskFile;
-    friend class DiskDirectory;
-
-    std::string mount_point;
-};
-
 class DiskFile : public FileBackend {
 public:
-    DiskFile(const DiskArchive& archive, const Path& path, const Mode mode);
+    DiskFile(FileUtil::IOFile&& file_, const Mode& mode_)
+        : file(new FileUtil::IOFile(std::move(file_))) {
+        mode.hex = mode_.hex;
+    }
 
-    ResultCode Open() override;
     ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
     u64 GetSize() const override;
@@ -68,20 +38,18 @@ public:
     }
 
 protected:
-    std::string path;
     Mode mode;
     std::unique_ptr<FileUtil::IOFile> file;
 };
 
 class DiskDirectory : public DirectoryBackend {
 public:
-    DiskDirectory(const DiskArchive& archive, const Path& path);
+    DiskDirectory(const std::string& path);
 
     ~DiskDirectory() override {
         Close();
     }
 
-    bool Open() override;
     u32 Read(const u32 count, Entry* entries) override;
 
     bool Close() const override {
@@ -89,7 +57,6 @@ public:
     }
 
 protected:
-    std::string path;
     u32 total_entries_in_directory;
     FileUtil::FSTEntry directory;
 
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
new file mode 100644
index 0000000000..fd1b07df02
--- /dev/null
+++ b/src/core/file_sys/errors.h
@@ -0,0 +1,40 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/result.h"
+
+namespace FileSys {
+
+const ResultCode ERROR_INVALID_PATH(ErrorDescription::FS_InvalidPath, ErrorModule::FS,
+                                    ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+const ResultCode ERROR_UNSUPPORTED_OPEN_FLAGS(ErrorDescription::FS_UnsupportedOpenFlags,
+                                              ErrorModule::FS, ErrorSummary::NotSupported,
+                                              ErrorLevel::Usage);
+const ResultCode ERROR_INVALID_OPEN_FLAGS(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS,
+                                          ErrorSummary::Canceled, ErrorLevel::Status);
+const ResultCode ERROR_INVALID_READ_FLAG(ErrorDescription::FS_InvalidReadFlag, ErrorModule::FS,
+                                         ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+const ResultCode ERROR_FILE_NOT_FOUND(ErrorDescription::FS_FileNotFound, ErrorModule::FS,
+                                      ErrorSummary::NotFound, ErrorLevel::Status);
+const ResultCode ERROR_PATH_NOT_FOUND(ErrorDescription::FS_PathNotFound, ErrorModule::FS,
+                                      ErrorSummary::NotFound, ErrorLevel::Status);
+const ResultCode ERROR_NOT_FOUND(ErrorDescription::FS_NotFound, ErrorModule::FS,
+                                 ErrorSummary::NotFound, ErrorLevel::Status);
+const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY(ErrorDescription::FS_UnexpectedFileOrDirectory,
+                                                    ErrorModule::FS, ErrorSummary::NotSupported,
+                                                    ErrorLevel::Usage);
+const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC(ErrorDescription::FS_NotAFile,
+                                                         ErrorModule::FS, ErrorSummary::Canceled,
+                                                         ErrorLevel::Status);
+const ResultCode ERROR_DIRECTORY_ALREADY_EXISTS(ErrorDescription::FS_DirectoryAlreadyExists,
+                                                ErrorModule::FS, ErrorSummary::NothingHappened,
+                                                ErrorLevel::Status);
+const ResultCode ERROR_FILE_ALREADY_EXISTS(ErrorDescription::FS_FileAlreadyExists, ErrorModule::FS,
+                                           ErrorSummary::NothingHappened, ErrorLevel::Status);
+const ResultCode ERROR_ALREADY_EXISTS(ErrorDescription::FS_AlreadyExists, ErrorModule::FS,
+                                      ErrorSummary::NothingHappened, ErrorLevel::Status);
+const ResultCode ERROR_DIRECTORY_NOT_EMPTY(ErrorDescription::FS_DirectoryNotEmpty, ErrorModule::FS,
+                                           ErrorSummary::Canceled, ErrorLevel::Status);
+
+} // namespace FileSys
diff --git a/src/core/file_sys/file_backend.h b/src/core/file_sys/file_backend.h
index ed997537ff..5e7c2bab48 100644
--- a/src/core/file_sys/file_backend.h
+++ b/src/core/file_sys/file_backend.h
@@ -18,12 +18,6 @@ public:
     FileBackend() {}
     virtual ~FileBackend() {}
 
-    /**
-     * Open the file
-     * @return Result of the file operation
-     */
-    virtual ResultCode Open() = 0;
-
     /**
      * Read data from the file
      * @param offset Offset in bytes to start reading data from
diff --git a/src/core/file_sys/ivfc_archive.cpp b/src/core/file_sys/ivfc_archive.cpp
index af59d296d7..2735d2e3c4 100644
--- a/src/core/file_sys/ivfc_archive.cpp
+++ b/src/core/file_sys/ivfc_archive.cpp
@@ -18,7 +18,7 @@ std::string IVFCArchive::GetName() const {
 }
 
 ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path,
-                                                              const Mode mode) const {
+                                                              const Mode& mode) const {
     return MakeResult<std::unique_ptr<FileBackend>>(
         std::make_unique<IVFCFile>(romfs_file, data_offset, data_size));
 }
@@ -31,22 +31,25 @@ ResultCode IVFCArchive::DeleteFile(const Path& path) const {
                       ErrorLevel::Status);
 }
 
-bool IVFCArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
+ResultCode IVFCArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
     LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).",
                  GetName().c_str());
-    return false;
+    // TODO(wwylele): Use correct error code
+    return ResultCode(-1);
 }
 
-bool IVFCArchive::DeleteDirectory(const Path& path) const {
+ResultCode IVFCArchive::DeleteDirectory(const Path& path) const {
     LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).",
                  GetName().c_str());
-    return false;
+    // TODO(wwylele): Use correct error code
+    return ResultCode(-1);
 }
 
-bool IVFCArchive::DeleteDirectoryRecursively(const Path& path) const {
+ResultCode IVFCArchive::DeleteDirectoryRecursively(const Path& path) const {
     LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).",
                  GetName().c_str());
-    return false;
+    // TODO(wwylele): Use correct error code
+    return ResultCode(-1);
 }
 
 ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const {
@@ -57,20 +60,22 @@ ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const {
                       ErrorLevel::Permanent);
 }
 
-bool IVFCArchive::CreateDirectory(const Path& path) const {
+ResultCode IVFCArchive::CreateDirectory(const Path& path) const {
     LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive (%s).",
                  GetName().c_str());
-    return false;
+    // TODO(wwylele): Use correct error code
+    return ResultCode(-1);
 }
 
-bool IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
+ResultCode IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
     LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).",
                  GetName().c_str());
-    return false;
+    // TODO(wwylele): Use correct error code
+    return ResultCode(-1);
 }
 
-std::unique_ptr<DirectoryBackend> IVFCArchive::OpenDirectory(const Path& path) const {
-    return std::make_unique<IVFCDirectory>();
+ResultVal<std::unique_ptr<DirectoryBackend>> IVFCArchive::OpenDirectory(const Path& path) const {
+    return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<IVFCDirectory>());
 }
 
 u64 IVFCArchive::GetFreeBytes() const {
diff --git a/src/core/file_sys/ivfc_archive.h b/src/core/file_sys/ivfc_archive.h
index 2fbb3a568f..e6fbdfb1ff 100644
--- a/src/core/file_sys/ivfc_archive.h
+++ b/src/core/file_sys/ivfc_archive.h
@@ -33,15 +33,15 @@ public:
     std::string GetName() const override;
 
     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
-                                                     const Mode mode) const override;
+                                                     const Mode& mode) const override;
     ResultCode DeleteFile(const Path& path) const override;
-    bool RenameFile(const Path& src_path, const Path& dest_path) const override;
-    bool DeleteDirectory(const Path& path) const override;
-    bool DeleteDirectoryRecursively(const Path& path) const override;
+    ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
+    ResultCode DeleteDirectory(const Path& path) const override;
+    ResultCode DeleteDirectoryRecursively(const Path& path) const override;
     ResultCode CreateFile(const Path& path, u64 size) const override;
-    bool CreateDirectory(const Path& path) const override;
-    bool RenameDirectory(const Path& src_path, const Path& dest_path) const override;
-    std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const override;
+    ResultCode CreateDirectory(const Path& path) const override;
+    ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
+    ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
     u64 GetFreeBytes() const override;
 
 protected:
@@ -55,9 +55,6 @@ public:
     IVFCFile(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size)
         : romfs_file(file), data_offset(offset), data_size(size) {}
 
-    ResultCode Open() override {
-        return RESULT_SUCCESS;
-    }
     ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
     u64 GetSize() const override;
@@ -75,9 +72,6 @@ private:
 
 class IVFCDirectory : public DirectoryBackend {
 public:
-    bool Open() override {
-        return false;
-    }
     u32 Read(const u32 count, Entry* entries) override {
         return 0;
     }
diff --git a/src/core/file_sys/path_parser.cpp b/src/core/file_sys/path_parser.cpp
new file mode 100644
index 0000000000..5a89b02b8d
--- /dev/null
+++ b/src/core/file_sys/path_parser.cpp
@@ -0,0 +1,98 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <set>
+#include "common/file_util.h"
+#include "common/string_util.h"
+#include "core/file_sys/path_parser.h"
+
+namespace FileSys {
+
+PathParser::PathParser(const Path& path) {
+    if (path.GetType() != LowPathType::Char && path.GetType() != LowPathType::Wchar) {
+        is_valid = false;
+        return;
+    }
+
+    auto path_string = path.AsString();
+    if (path_string.size() == 0 || path_string[0] != '/') {
+        is_valid = false;
+        return;
+    }
+
+    // Filter out invalid characters for the host system.
+    // Although some of these characters are valid on 3DS, they are unlikely to be used by games.
+    if (std::find_if(path_string.begin(), path_string.end(), [](char c) {
+            static const std::set<char> invalid_chars{'<', '>', '\\', '|', ':', '\"', '*', '?'};
+            return invalid_chars.find(c) != invalid_chars.end();
+        }) != path_string.end()) {
+        is_valid = false;
+        return;
+    }
+
+    Common::SplitString(path_string, '/', path_sequence);
+
+    auto begin = path_sequence.begin();
+    auto end = path_sequence.end();
+    end = std::remove_if(begin, end, [](std::string& str) { return str == "" || str == "."; });
+    path_sequence = std::vector<std::string>(begin, end);
+
+    // checks if the path is out of bounds.
+    int level = 0;
+    for (auto& node : path_sequence) {
+        if (node == "..") {
+            --level;
+            if (level < 0) {
+                is_valid = false;
+                return;
+            }
+        } else {
+            ++level;
+        }
+    }
+
+    is_valid = true;
+    is_root = level == 0;
+}
+
+PathParser::HostStatus PathParser::GetHostStatus(const std::string& mount_point) const {
+    auto path = mount_point;
+    if (!FileUtil::IsDirectory(path))
+        return InvalidMountPoint;
+    if (path_sequence.empty()) {
+        return DirectoryFound;
+    }
+
+    for (auto iter = path_sequence.begin(); iter != path_sequence.end() - 1; iter++) {
+        if (path.back() != '/')
+            path += '/';
+        path += *iter;
+
+        if (!FileUtil::Exists(path))
+            return PathNotFound;
+        if (FileUtil::IsDirectory(path))
+            continue;
+        return FileInPath;
+    }
+
+    path += "/" + path_sequence.back();
+    if (!FileUtil::Exists(path))
+        return NotFound;
+    if (FileUtil::IsDirectory(path))
+        return DirectoryFound;
+    return FileFound;
+}
+
+std::string PathParser::BuildHostPath(const std::string& mount_point) const {
+    std::string path = mount_point;
+    for (auto& node : path_sequence) {
+        if (path.back() != '/')
+            path += '/';
+        path += node;
+    }
+    return path;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/path_parser.h b/src/core/file_sys/path_parser.h
new file mode 100644
index 0000000000..9908025797
--- /dev/null
+++ b/src/core/file_sys/path_parser.h
@@ -0,0 +1,61 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include "core/file_sys/archive_backend.h"
+
+namespace FileSys {
+
+/**
+ * A helper class parsing and verifying a string-type Path.
+ * Every archives with a sub file system should use this class to parse the path argument and check
+ * the status of the file / directory in question on the host file system.
+ */
+class PathParser {
+public:
+    PathParser(const Path& path);
+
+    /**
+     * Checks if the Path is valid.
+     * This function should be called once a PathParser is constructed.
+     * A Path is valid if:
+     *  - it is a string path (with type LowPathType::Char or LowPathType::Wchar),
+     *  - it starts with "/" (this seems a hard requirement in real 3DS),
+     *  - it doesn't contain invalid characters, and
+     *  - it doesn't go out of the root directory using "..".
+     */
+    bool IsValid() const {
+        return is_valid;
+    }
+
+    /// Checks if the Path represents the root directory.
+    bool IsRootDirectory() const {
+        return is_root;
+    }
+
+    enum HostStatus {
+        InvalidMountPoint,
+        PathNotFound,   // "/a/b/c" when "a" doesn't exist
+        FileInPath,     // "/a/b/c" when "a" is a file
+        FileFound,      // "/a/b/c" when "c" is a file
+        DirectoryFound, // "/a/b/c" when "c" is a directory
+        NotFound        // "/a/b/c" when "a/b/" exists but "c" doesn't exist
+    };
+
+    /// Checks the status of the specified file / directory by the Path on the host file system.
+    HostStatus GetHostStatus(const std::string& mount_point) const;
+
+    /// Builds a full path on the host file system.
+    std::string BuildHostPath(const std::string& mount_point) const;
+
+private:
+    std::vector<std::string> path_sequence;
+    bool is_valid{};
+    bool is_root{};
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/savedata_archive.cpp b/src/core/file_sys/savedata_archive.cpp
new file mode 100644
index 0000000000..f2e6a06bc9
--- /dev/null
+++ b/src/core/file_sys/savedata_archive.cpp
@@ -0,0 +1,283 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/file_util.h"
+#include "core/file_sys/disk_archive.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/path_parser.h"
+#include "core/file_sys/savedata_archive.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& path,
+                                                                  const Mode& mode) const {
+    LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
+
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    if (mode.hex == 0) {
+        LOG_ERROR(Service_FS, "Empty open mode");
+        return ERROR_UNSUPPORTED_OPEN_FLAGS;
+    }
+
+    if (mode.create_flag && !mode.write_flag) {
+        LOG_ERROR(Service_FS, "Create flag set but write flag not set");
+        return ERROR_UNSUPPORTED_OPEN_FLAGS;
+    }
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_FILE_NOT_FOUND;
+    case PathParser::PathNotFound:
+        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+        return ERROR_PATH_NOT_FOUND;
+    case PathParser::FileInPath:
+    case PathParser::DirectoryFound:
+        LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str());
+        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+    case PathParser::NotFound:
+        if (!mode.create_flag) {
+            LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.",
+                      full_path.c_str());
+            return ERROR_FILE_NOT_FOUND;
+        } else {
+            // Create the file
+            FileUtil::CreateEmptyFile(full_path);
+        }
+        break;
+    }
+
+    FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
+    if (!file.IsOpen()) {
+        LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str());
+        return ERROR_FILE_NOT_FOUND;
+    }
+
+    auto disk_file = std::make_unique<DiskFile>(std::move(file), mode);
+    return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file));
+}
+
+ResultCode SaveDataArchive::DeleteFile(const Path& path) const {
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_FILE_NOT_FOUND;
+    case PathParser::PathNotFound:
+        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+        return ERROR_PATH_NOT_FOUND;
+    case PathParser::FileInPath:
+    case PathParser::DirectoryFound:
+    case PathParser::NotFound:
+        LOG_ERROR(Service_FS, "File not found %s", full_path.c_str());
+        return ERROR_FILE_NOT_FOUND;
+    }
+
+    if (FileUtil::Delete(full_path)) {
+        return RESULT_SUCCESS;
+    }
+
+    LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str());
+    return ERROR_FILE_NOT_FOUND;
+}
+
+ResultCode SaveDataArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
+    if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) {
+        return RESULT_SUCCESS;
+    }
+
+    // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
+    // exist or similar. Verify.
+    return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
+                      ErrorSummary::NothingHappened, ErrorLevel::Status);
+}
+
+template <typename T>
+static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point,
+                                        T deleter) {
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    if (path_parser.IsRootDirectory())
+        return ERROR_DIRECTORY_NOT_EMPTY;
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_PATH_NOT_FOUND;
+    case PathParser::PathNotFound:
+    case PathParser::NotFound:
+        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+        return ERROR_PATH_NOT_FOUND;
+    case PathParser::FileInPath:
+    case PathParser::FileFound:
+        LOG_ERROR(Service_FS, "Unexpected file or directory %s", full_path.c_str());
+        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+    }
+
+    if (deleter(full_path)) {
+        return RESULT_SUCCESS;
+    }
+
+    LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str());
+    return ERROR_DIRECTORY_NOT_EMPTY;
+}
+
+ResultCode SaveDataArchive::DeleteDirectory(const Path& path) const {
+    return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir);
+}
+
+ResultCode SaveDataArchive::DeleteDirectoryRecursively(const Path& path) const {
+    return DeleteDirectoryHelper(
+        path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
+}
+
+ResultCode SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const {
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_FILE_NOT_FOUND;
+    case PathParser::PathNotFound:
+        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+        return ERROR_PATH_NOT_FOUND;
+    case PathParser::FileInPath:
+        LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
+        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+    case PathParser::DirectoryFound:
+    case PathParser::FileFound:
+        LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
+        return ERROR_FILE_ALREADY_EXISTS;
+    }
+
+    if (size == 0) {
+        FileUtil::CreateEmptyFile(full_path);
+        return RESULT_SUCCESS;
+    }
+
+    FileUtil::IOFile file(full_path, "wb");
+    // Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
+    // We do this by seeking to the right size, then writing a single null byte.
+    if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
+        return RESULT_SUCCESS;
+    }
+
+    LOG_ERROR(Service_FS, "Too large file");
+    return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource,
+                      ErrorLevel::Info);
+}
+
+ResultCode SaveDataArchive::CreateDirectory(const Path& path) const {
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_FILE_NOT_FOUND;
+    case PathParser::PathNotFound:
+        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+        return ERROR_PATH_NOT_FOUND;
+    case PathParser::FileInPath:
+        LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
+        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+    case PathParser::DirectoryFound:
+    case PathParser::FileFound:
+        LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
+        return ERROR_DIRECTORY_ALREADY_EXISTS;
+    }
+
+    if (FileUtil::CreateDir(mount_point + path.AsString())) {
+        return RESULT_SUCCESS;
+    }
+
+    LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str());
+    return ResultCode(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled,
+                      ErrorLevel::Status);
+}
+
+ResultCode SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
+    if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString()))
+        return RESULT_SUCCESS;
+
+    // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
+    // exist or similar. Verify.
+    return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
+                      ErrorSummary::NothingHappened, ErrorLevel::Status);
+}
+
+ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory(
+    const Path& path) const {
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_FILE_NOT_FOUND;
+    case PathParser::PathNotFound:
+    case PathParser::NotFound:
+        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+        return ERROR_PATH_NOT_FOUND;
+    case PathParser::FileInPath:
+    case PathParser::FileFound:
+        LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
+        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+    }
+
+    auto directory = std::make_unique<DiskDirectory>(full_path);
+    return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
+}
+
+u64 SaveDataArchive::GetFreeBytes() const {
+    // TODO: Stubbed to return 1GiB
+    return 1024 * 1024 * 1024;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/savedata_archive.h b/src/core/file_sys/savedata_archive.h
new file mode 100644
index 0000000000..2fb6c452aa
--- /dev/null
+++ b/src/core/file_sys/savedata_archive.h
@@ -0,0 +1,43 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include "core/file_sys/archive_backend.h"
+#include "core/file_sys/directory_backend.h"
+#include "core/file_sys/file_backend.h"
+#include "core/hle/result.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+/// Archive backend for general save data archive type (SaveData and SystemSaveData)
+class SaveDataArchive : public ArchiveBackend {
+public:
+    SaveDataArchive(const std::string& mount_point_) : mount_point(mount_point_) {}
+
+    std::string GetName() const override {
+        return "SaveDataArchive: " + mount_point;
+    }
+
+    ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
+                                                     const Mode& mode) const override;
+    ResultCode DeleteFile(const Path& path) const override;
+    ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
+    ResultCode DeleteDirectory(const Path& path) const override;
+    ResultCode DeleteDirectoryRecursively(const Path& path) const override;
+    ResultCode CreateFile(const Path& path, u64 size) const override;
+    ResultCode CreateDirectory(const Path& path) const override;
+    ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
+    ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
+    u64 GetFreeBytes() const override;
+
+protected:
+    std::string mount_point;
+};
+
+} // namespace FileSys
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 7f8d8e00d9..f7356f9d84 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -20,15 +20,24 @@ enum class ErrorDescription : u32 {
     OS_InvalidBufferDescriptor = 48,
     WrongAddress = 53,
     FS_ArchiveNotMounted = 101,
+    FS_FileNotFound = 112,
+    FS_PathNotFound = 113,
     FS_NotFound = 120,
+    FS_FileAlreadyExists = 180,
+    FS_DirectoryAlreadyExists = 185,
     FS_AlreadyExists = 190,
     FS_InvalidOpenFlags = 230,
+    FS_DirectoryNotEmpty = 240,
     FS_NotAFile = 250,
     FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive
     OutofRangeOrMisalignedAddress =
         513, // TODO(purpasmart): Check if this name fits its actual usage
     GPU_FirstInitialization = 519,
+    FS_InvalidReadFlag = 700,
     FS_InvalidPath = 702,
+    FS_WriteBeyondEnd = 705,
+    FS_UnsupportedOpenFlags = 760,
+    FS_UnexpectedFileOrDirectory = 770,
     InvalidSection = 1000,
     TooLarge = 1001,
     NotAuthorized = 1002,
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp
index d3d0f3b55a..d554c3f54f 100644
--- a/src/core/hle/service/cfg/cfg.cpp
+++ b/src/core/hle/service/cfg/cfg.cpp
@@ -360,7 +360,7 @@ ResultCode CreateConfigInfoBlk(u32 block_id, u16 size, u16 flags, const void* da
 }
 
 ResultCode DeleteConfigNANDSaveFile() {
-    FileSys::Path path("config");
+    FileSys::Path path("/config");
     return Service::FS::DeleteFileFromArchive(cfg_system_save_data_archive, path);
 }
 
@@ -369,7 +369,7 @@ ResultCode UpdateConfigNANDSavegame() {
     mode.write_flag.Assign(1);
     mode.create_flag.Assign(1);
 
-    FileSys::Path path("config");
+    FileSys::Path path("/config");
 
     auto config_result = Service::FS::OpenFileFromArchive(cfg_system_save_data_archive, path, mode);
     ASSERT_MSG(config_result.Succeeded(), "could not open file");
@@ -383,8 +383,9 @@ ResultCode UpdateConfigNANDSavegame() {
 ResultCode FormatConfig() {
     ResultCode res = DeleteConfigNANDSaveFile();
     // The delete command fails if the file doesn't exist, so we have to check that too
-    if (!res.IsSuccess() && res.description != ErrorDescription::FS_NotFound)
+    if (!res.IsSuccess() && res.description != ErrorDescription::FS_FileNotFound) {
         return res;
+    }
     // Delete the old data
     cfg_config_file_buffer.fill(0);
     // Create the header
@@ -510,7 +511,7 @@ ResultCode LoadConfigNANDSaveFile() {
 
     cfg_system_save_data_archive = *archive_result;
 
-    FileSys::Path config_path("config");
+    FileSys::Path config_path("/config");
     FileSys::Mode open_mode = {};
     open_mode.read_flag.Assign(1);
 
diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp
index 7f9696bfb2..4c29784e84 100644
--- a/src/core/hle/service/fs/archive.cpp
+++ b/src/core/hle/service/fs/archive.cpp
@@ -15,9 +15,10 @@
 #include "common/logging/log.h"
 #include "core/file_sys/archive_backend.h"
 #include "core/file_sys/archive_extsavedata.h"
+#include "core/file_sys/archive_ncch.h"
 #include "core/file_sys/archive_savedata.h"
-#include "core/file_sys/archive_savedatacheck.h"
 #include "core/file_sys/archive_sdmc.h"
+#include "core/file_sys/archive_sdmcwriteonly.h"
 #include "core/file_sys/archive_systemsavedata.h"
 #include "core/file_sys/directory_backend.h"
 #include "core/file_sys/file_backend.h"
@@ -338,17 +339,11 @@ ResultCode RenameFileBetweenArchives(ArchiveHandle src_archive_handle,
         return ERR_INVALID_ARCHIVE_HANDLE;
 
     if (src_archive == dest_archive) {
-        if (src_archive->RenameFile(src_path, dest_path))
-            return RESULT_SUCCESS;
+        return src_archive->RenameFile(src_path, dest_path);
     } else {
         // TODO: Implement renaming across archives
         return UnimplementedFunction(ErrorModule::FS);
     }
-
-    // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
-    // exist or similar. Verify.
-    return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
-                      ErrorSummary::NothingHappened, ErrorLevel::Status);
 }
 
 ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) {
@@ -356,10 +351,7 @@ ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy
     if (archive == nullptr)
         return ERR_INVALID_ARCHIVE_HANDLE;
 
-    if (archive->DeleteDirectory(path))
-        return RESULT_SUCCESS;
-    return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
-                      ErrorSummary::Canceled, ErrorLevel::Status);
+    return archive->DeleteDirectory(path);
 }
 
 ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle,
@@ -368,10 +360,7 @@ ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle,
     if (archive == nullptr)
         return ERR_INVALID_ARCHIVE_HANDLE;
 
-    if (archive->DeleteDirectoryRecursively(path))
-        return RESULT_SUCCESS;
-    return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
-                      ErrorSummary::Canceled, ErrorLevel::Status);
+    return archive->DeleteDirectoryRecursively(path);
 }
 
 ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path,
@@ -388,10 +377,7 @@ ResultCode CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy
     if (archive == nullptr)
         return ERR_INVALID_ARCHIVE_HANDLE;
 
-    if (archive->CreateDirectory(path))
-        return RESULT_SUCCESS;
-    return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
-                      ErrorSummary::Canceled, ErrorLevel::Status);
+    return archive->CreateDirectory(path);
 }
 
 ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle,
@@ -404,17 +390,11 @@ ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle,
         return ERR_INVALID_ARCHIVE_HANDLE;
 
     if (src_archive == dest_archive) {
-        if (src_archive->RenameDirectory(src_path, dest_path))
-            return RESULT_SUCCESS;
+        return src_archive->RenameDirectory(src_path, dest_path);
     } else {
         // TODO: Implement renaming across archives
         return UnimplementedFunction(ErrorModule::FS);
     }
-
-    // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
-    // exist or similar. Verify.
-    return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
-                      ErrorSummary::NothingHappened, ErrorLevel::Status);
 }
 
 ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle,
@@ -423,13 +403,11 @@ ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle a
     if (archive == nullptr)
         return ERR_INVALID_ARCHIVE_HANDLE;
 
-    std::unique_ptr<FileSys::DirectoryBackend> backend = archive->OpenDirectory(path);
-    if (backend == nullptr) {
-        return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound,
-                          ErrorLevel::Permanent);
-    }
+    auto backend = archive->OpenDirectory(path);
+    if (backend.Failed())
+        return backend.Code();
 
-    auto directory = Kernel::SharedPtr<Directory>(new Directory(std::move(backend), path));
+    auto directory = Kernel::SharedPtr<Directory>(new Directory(backend.MoveFrom(), path));
     return MakeResult<Kernel::SharedPtr<Directory>>(std::move(directory));
 }
 
@@ -549,6 +527,13 @@ void RegisterArchiveTypes() {
         LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s",
                   sdmc_directory.c_str());
 
+    auto sdmcwo_factory = std::make_unique<FileSys::ArchiveFactory_SDMCWriteOnly>(sdmc_directory);
+    if (sdmcwo_factory->Initialize())
+        RegisterArchiveType(std::move(sdmcwo_factory), ArchiveIdCode::SDMCWriteOnly);
+    else
+        LOG_ERROR(Service_FS, "Can't instantiate SDMCWriteOnly archive with path %s",
+                  sdmc_directory.c_str());
+
     // Create the SaveData archive
     auto savedata_factory = std::make_unique<FileSys::ArchiveFactory_SaveData>(sdmc_directory);
     RegisterArchiveType(std::move(savedata_factory), ArchiveIdCode::SaveData);
@@ -569,10 +554,9 @@ void RegisterArchiveTypes() {
         LOG_ERROR(Service_FS, "Can't instantiate SharedExtSaveData archive with path %s",
                   sharedextsavedata_factory->GetMountPoint().c_str());
 
-    // Create the SaveDataCheck archive, basically a small variation of the RomFS archive
-    auto savedatacheck_factory =
-        std::make_unique<FileSys::ArchiveFactory_SaveDataCheck>(nand_directory);
-    RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::SaveDataCheck);
+    // Create the NCCH archive, basically a small variation of the RomFS archive
+    auto savedatacheck_factory = std::make_unique<FileSys::ArchiveFactory_NCCH>(nand_directory);
+    RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::NCCH);
 
     auto systemsavedata_factory =
         std::make_unique<FileSys::ArchiveFactory_SystemSaveData>(nand_directory);
diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h
index 41a76285cd..21ed9717bf 100644
--- a/src/core/hle/service/fs/archive.h
+++ b/src/core/hle/service/fs/archive.h
@@ -33,7 +33,7 @@ enum class ArchiveIdCode : u32 {
     SystemSaveData = 0x00000008,
     SDMC = 0x00000009,
     SDMCWriteOnly = 0x0000000A,
-    SaveDataCheck = 0x2345678A,
+    NCCH = 0x2345678A,
 };
 
 /// Media types for the archives
diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp
index 6e6b633297..cc859c14c0 100644
--- a/src/core/hle/service/ptm/ptm.cpp
+++ b/src/core/hle/service/ptm/ptm.cpp
@@ -128,7 +128,7 @@ void Init() {
             Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path);
         ASSERT_MSG(archive_result.Succeeded(), "Could not open the PTM SharedExtSaveData archive!");
 
-        FileSys::Path gamecoin_path("gamecoin.dat");
+        FileSys::Path gamecoin_path("/gamecoin.dat");
         FileSys::Mode open_mode = {};
         open_mode.write_flag.Assign(1);
         open_mode.create_flag.Assign(1);
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 457c55571c..89237e4770 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,5 +1,7 @@
 set(SRCS
+            glad.cpp
             tests.cpp
+            core/file_sys/path_parser.cpp
             )
 
 set(HEADERS
diff --git a/src/tests/core/file_sys/path_parser.cpp b/src/tests/core/file_sys/path_parser.cpp
new file mode 100644
index 0000000000..2b543e438a
--- /dev/null
+++ b/src/tests/core/file_sys/path_parser.cpp
@@ -0,0 +1,38 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <catch.hpp>
+#include "common/file_util.h"
+#include "core/file_sys/path_parser.h"
+
+namespace FileSys {
+
+TEST_CASE("PathParser", "[core][file_sys]") {
+    REQUIRE(!PathParser(Path(std::vector<u8>{})).IsValid());
+    REQUIRE(!PathParser(Path("a")).IsValid());
+    REQUIRE(!PathParser(Path("/|")).IsValid());
+    REQUIRE(PathParser(Path("/a")).IsValid());
+    REQUIRE(!PathParser(Path("/a/b/../../c/../../d")).IsValid());
+    REQUIRE(PathParser(Path("/a/b/../c/../../d")).IsValid());
+    REQUIRE(PathParser(Path("/")).IsRootDirectory());
+    REQUIRE(!PathParser(Path("/a")).IsRootDirectory());
+    REQUIRE(PathParser(Path("/a/..")).IsRootDirectory());
+}
+
+TEST_CASE("PathParser - Host file system", "[core][file_sys]") {
+    std::string test_dir = "./test";
+    FileUtil::CreateDir(test_dir);
+    FileUtil::CreateDir(test_dir + "/z");
+    FileUtil::CreateEmptyFile(test_dir + "/a");
+
+    REQUIRE(PathParser(Path("/a")).GetHostStatus(test_dir) == PathParser::FileFound);
+    REQUIRE(PathParser(Path("/b")).GetHostStatus(test_dir) == PathParser::NotFound);
+    REQUIRE(PathParser(Path("/z")).GetHostStatus(test_dir) == PathParser::DirectoryFound);
+    REQUIRE(PathParser(Path("/a/c")).GetHostStatus(test_dir) == PathParser::FileInPath);
+    REQUIRE(PathParser(Path("/b/c")).GetHostStatus(test_dir) == PathParser::PathNotFound);
+
+    FileUtil::DeleteDirRecursively(test_dir);
+}
+
+} // namespace FileSys
diff --git a/src/tests/glad.cpp b/src/tests/glad.cpp
new file mode 100644
index 0000000000..b0b0164400
--- /dev/null
+++ b/src/tests/glad.cpp
@@ -0,0 +1,14 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <catch.hpp>
+#include <glad/glad.h>
+
+// This is not an actual test, but a work-around for issue #2183.
+// If tests uses functions in core but doesn't explicitly use functions in glad, the linker of macOS
+// will error about undefined references from video_core to glad. So we explicitly use a glad
+// function here to shut up the linker.
+TEST_CASE("glad fake test", "[dummy]") {
+    REQUIRE(&gladLoadGL != nullptr);
+}